File-Based Routing

Blitz uses a file-system based router that's built on


  • All components in pages/ are mapped to a URL. (Pages documentation)
  • All http handlers in api/ are mapped to a URL. (API routes documentation)
  • Queries and mutations are automatically exposed as an API endpoint
    • The app/projects/queries/getProjects.js query is exposed at /api/projects/queries/getProjects
    • See the RPC Specification for more details

Index routes

The router will automatically route files named

index to the root of the directory.

  • app/pages/index.js/
  • app/pages/blog/index.js/blog

Nested routes

The router supports nested files. If you create a nested folder structure files will be automatically routed in the same way still.

  • app/pages/blog/first-post.js/blog/first-post
  • app/pages/dashboard/settings/username.js/dashboard/settings/username

Dynamic route segments

To match a dynamic segment you can use the bracket syntax. This allows you to match named parameters.

  • app/pages/blog/[slug].js/blog/:slug (/blog/hello-world)
  • app/pages/[username]/settings.js/:username/settings (/foo/settings)
  • app/pages/post/[...all].js/post/* (/post/2020/id/title)

Consider the following page


import {useParam} from "blitz"
const Post = () => {
const pid = useParam("pid")
return <p>Post: {pid}</p>
export default Post

Any route like

/post/1, /post/abc, etc. will be matched by app/pages/post/[pid].js. The matched path parameter will be sent as a query parameter to the page, and it will be merged with the other query parameters.

For example, the route

/post/abc will have the following query object:

{"pid": "abc"}

Similarly, the route

/post/abc?foo=bar will have the following query object:

{"foo": "bar", "pid": "abc"}

However, route parameters will override query parameters with the same name. For example, the route

/post/abc?pid=123 will have the following query object:

{"pid": "abc"}

Multiple dynamic route segments work the same way. The page

app/pages/post/[pid]/[comment].js will match the route /post/abc/a-comment and its query object will be:

{"pid": "abc", "comment": "a-comment"}

Catch all routes

Dynamic routes can be extended to catch all paths by adding three dots (

...) inside the brackets. For example:

  • app/pages/post/[...slug].js matches /post/a, but also /post/a/b, /post/a/b/c and so on.

Note: You can use names other than

slug, such as: [...param]

Matched parameters will be sent as a query parameter (

slug in the example) to the page, and it will always be an array, so, the path /post/a will have the following query object:

{"slug": ["a"]}

And in the case of

/post/a/b, and any other matching path, new parameters will be added to the array, like so:

{"slug": ["a", "b"]}

Optional catch all routes

Catch all routes can be made optional by including the parameter in double brackets (


For example,

app/pages/post/[[...slug]].js will match /post, /post/a, /post/a/b, and so on.


query objects are as follows:

{ } // GET `/post` (empty object)
{ "slug": ["a"] } // `GET /post/a` (single-element array)
{ "slug": ["a", "b"] } // `GET /post/a/b` (multi-element array)


  • Predefined routes take precedence over dynamic routes, and dynamic routes over catch all routes. Take a look at the following examples:

    • app/pages/post/create.js - Will match /post/create
    • app/pages/post/[pid].js - Will match /post/1, /post/abc, etc. But not /post/create
    • app/pages/post/[...slug].js - Will match /post/1/2, /post/a/b/c, etc. But not /post/create, /post/abc
  • Pages that are statically optimized by

    Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}).

    After hydration, Blitz will trigger an update to your application to provide the route parameters in the query object.

Idea for improving this page?Edit it on GitHub
Bytes Newsletter