question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[RFC] Dynamic Routes

See original GitHub issue

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

  1. Leverage convention to provide URL Slug support that is easy to reason about
  2. Cover a majority of use-cases observed in the wild
  3. Eliminate the need of a custom server to support /blog/:post
  4. Validate <Link /> route transitions when possible
  5. Avoid an implementation that requires a route manifest
  6. 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:

  1. A filename or directory name that is wrapped with [] would be considered a named parameter
  2. Explicit route segments would take priority over dynamic segments, matched from left-to-right
  3. Route parameters would be required, never optional
  4. 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 Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:296
  • Comments:90 (42 by maintainers)

github_iconTop GitHub Comments

125reactions
Timercommented, Jun 19, 2019

Poll: To express interest in optional parameters, please react with a “+1” this comment.

Note: Optional parameters are already possible with this RFC, they just do not have an explicit syntax (see Caveats section).

62reactions
Timercommented, Jun 19, 2019

Poll: To express interest in catch-all parameters, please react with a “+1” this comment.

Note: Please share your use case for catch-all parameters in this thread! We’d love to understand the problem-space more.

Read more comments on GitHub >

github_iconTop Results From Across the Web

IETF RFC 4728 - The Dynamic Source Routing Protocol (DSR ...
The protocol allows multiple routes to any destination and allows each sender to select and control the routes used in routing its packets,...
Read more >
RFC 3884: Use of IPsec Transport Mode for ... - » RFC Editor
1. VN Routing Support and Complexity This section investigates whether the three alternatives and IIPtran support VN routing, especially dynamic routing based ...
Read more >
7.4 STATIC ROUTING - Freesoft
A router that supports a dynamic routing protocol MUST allow static routes to be defined with any metric valid for the routing protocol...
Read more >
[RFC] Dynamic Routes · Issue #7607 · vercel/next.js - GitHub
Explicit route segments would take priority over dynamic segments, matched from left-to-right; Route parameters would be required, never ...
Read more >
Blog - Layouts RFC | Next.js
Each page file exports a React Component and has an associated route based on its file name. For example: Dynamic Routes: Next.js supports ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found