Issue 1:
Dynamic Routes

Background

Dynamic routing (also known as URL Slugs or Pretty/Clean URLs) has been a long-time requested feature of Next.js.

Current solutions involve placing a L7 proxy, custom server, or user-land middleware in-front of your application. None of these solutions offer a sufficiently ergonomic developer experience.

Additionally, users reaching for a custom server inadvertently opt-out of advanced framework-level features like per-page serverless functions.

Goals

Leverage convention to provide URL Slug support that is easy to reason about

Cover a majority of use-cases observed in the wild

Eliminate the need of a custom server to support /blog/:post

Validate <Link /> route transitions when possible

Avoid an implementation that requires a route manifest

Routes must be expressible through the filesystem

Proposal

Next.js should support named URL parameters that match an entire URL segment. These routes would be expressed via the filesystem:

A filename or directory name that is wrapped with [] would be considered a named parameter

Explicit route segments would take priority over dynamic segments, matched from left-to-right

Route parameters would be required, never optional

Route parameters will be merged into the query object (accessible from getInitialProps or router via withRouter) — these parameters can not be overridden by a query parameter

To help understand this proposal, let's examine the following file tree:

pages/
├── [root].js
├── blog/
│ └── [id].js
├── customers/
│ ├── [customer]/
│ │ ├── [post].js
│ │ ├── index.js
│ │ └── profile.js
│ ├── index.js
│ └── new.js
├── index.js
└── terms.js

Next.js would produce the following routes, registered in the following order:

;[
{ path: '/', page: '/index.js' },
{ path: '/blog/:id', page: '/blog/[id].js' },
{ path: '/customers', page: '/customers/index.js' },
{ path: '/customers/new', page: '/customers/new.js' },
{ path: '/customers/:customer', page: '/customers/[customer]/index.js' },
{
path: '/customers/:customer/profile',
page: '/customers/[customer]/profile.js',
},
{ path: '/customers/:customer/:post', page: '/customers/[customer]/[post].js' },
{ path: '/terms', page: '/terms.js' },
{ path: '/:root', page: '/[root].js' },
]

Usage Examples

These examples all assume a page with the filename pages/blog/[id].js:

Navigating to the Page with <Link />

<Link href="/blog/[id]" as="/blog/how-to-use-dynamic-routes">
<a>
Next.js: Dynamic Routing{' '}
<span role="img" aria-label="Party Popper">
🎉
</span>
</a>
</Link>

The above example will transition to the /blog/[id].js page and provide the following query object to the Router:

{
id: 'how-to-use-dynamic-routes'
}

Reading Named Parameters from Router

import { useRouter } from 'next/router'

function BlogPost() {
const router = useRouter()
// `blogId` will be `'how-to-use-dynamic-routes'` when rendering
// `/blog/how-to-use-dynamic-routes`
const blogId = router.query.id
return <main>This is blog post {blogId}.</main>
}

export default BlogPost

Note: you can also use withRouter.

Reading Named Parameters in getInitialProps

function BlogPost({ blogText }) {
return <main>{blogText}</main>
}

BlogPost.getInitialProps = async function({ query }) {
// `blogId` will be `'how-to-use-dynamic-routes'` when rendering
// `/blog/how-to-use-dynamic-routes`
const blogId = query.id

const { text } = await fetch(
'/api/blog/content?id=' + encodeURIComponent(blogId)
).then(res => res.json())

return { blogText: text }
}

export default BlogPost

Caveats

Optional route parameters are not expressible through the filesystem.

You can emulate an optional route parameter by creating a stub page that exports the parameter version (or vice versa). This increases the visibility of your application's routes when inspecting the filesystem.

// pages/blog/comments.js
// (the optional version of `pages/blog/[id]/comments.js`)
export { default } from './[id]/comments.js'

Named parameters cannot appear in the middle of a route name.

This means a page named blog-[id].js would be interpreted literally and not matched by /blog-1. You can either restructure your page to be /blog/[id].js or turn the entire URL Segment into a named parameter and handle stripping blog- in your application's code.

Alternatives

Denote URL Slugs with insert symbol here instead of []

There are very few symbols available for use to represent a named parameter on the filesystem. Unfortunately, the most recognized way of defining a named parameter (:name) is not a valid filename.

While surveying prior art, the most common symbols used to denote a parameter were _, $ and [].

We ruled out _ because _ is typically indicative of an internal route that is not publicly routable (e.g. _app, _document, /_src, /_logs).

We also ruled out $ because it is a sigil in bash for parameter expansion.

Leverage path-to-regexp for comprehensive support

Most of the symbols required to express regex are not valid filenames. Additionally, complex regexes are sensitive to route ordering for prioritization. The filesystem cannot express order nor contain regex symbols.

In the future, we may allow path-to-regexp routes defined in next.config.js or similar. This is currently out of scope for this proposal.

Future Exploration

Catch-All Parameters

In the future, we may consider adding catch-all parameters. With what we know thus far, these parameters must be at the end of the URL and would potentially use % to denote a catch-all route (e.g. pages/website-builder/[customerName]/%.tsx).

Issue 2:
Currently I am working on an i18n solution with my nextjs app. In general I don't want to include all the text strings with the initial page load. Especially with huge pages (think of wikipedia or a terms and condition page). All those text strings are already part of the server side rendered document. Why include it again in __NEXT_DATA__ and add it into the initial page load, when it is not needed for the initial display of the page?

I am not very familiar with all the internals but this goes for a lot of data. Lots of us are using redux. The initial redux store doesn't have to be part of the first load.

This might not be a huge performance improvement for many websites but for some of them it is. And I don't see any benefit in serving this data with the first load.

What do you think?