<h1 dir="auto">Dynamic Routes</h1>
<h2 dir="auto">Background</h2>
<p dir="auto">Dynamic routing (also known as URL Slugs or Pretty/Clean URLs) <a href="https://github.com/zeit/next.js/issues/25" data-hovercard-type="issue" data-hovercard-url="/vercel/next.js/issues/25/hovercard">has</a> <a href="https://github.com/zeit/next.js/issues/59" data-hovercard-type="issue" data-hovercard-url="/vercel/next.js/issues/59/hovercard">been a</a> <a href="https://github.com/zeit/next.js/issues/4989" data-hovercard-type="issue" data-hovercard-url="/vercel/next.js/issues/4989/hovercard">long-time</a> <a href="https://github.com/zeit/next.js/issues?page=1&amp;q=is%3Aissue+dynamic+routing">requested feature</a> of Next.js.</p>
<p dir="auto">Current solutions involve placing a <a href="https://zeit.co/docs/v2/deployments/routes" rel="nofollow">L7 proxy</a>, <a href="https://github.com/zeit/next.js#custom-server-and-routing">custom server</a>, or <a href="https://www.npmjs.com/package/next-routes" rel="nofollow">user-land middleware</a> in-front of your application. None of these solutions offer a sufficiently <em>ergonomic</em> developer experience.</p>
<p dir="auto">Additionally, users reaching for a custom server inadvertently opt-out of advanced framework-level features like per-page serverless functions.</p>
<h2 dir="auto">Goals</h2>
<ol dir="auto">
<li>Leverage convention to provide URL Slug support that is easy to reason about</li>
<li>Cover a majority of use-cases observed in the wild</li>
<li>Eliminate the need of a custom server to support <code class="notranslate">/blog/:post</code></li>
<li>Validate <code class="notranslate">&lt;Link /&gt;</code> route transitions when possible</li>
<li>Avoid an implementation that requires a route manifest</li>
<li>Routes must be expressible through the filesystem</li>
</ol>
<h2 dir="auto">Proposal</h2>
<p dir="auto">Next.js should support named URL parameters that match an <a href="https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Definition" rel="nofollow"><strong>entire</strong> URL segment</a>. These routes would be expressed via the filesystem:</p>
<ol dir="auto">
<li>A filename or directory name that is <strong>wrapped with <code class="notranslate">[]</code></strong> would be considered a named parameter</li>
<li>Explicit route segments would take priority over dynamic segments, matched from left-to-right</li>
<li>Route parameters would be <strong>required</strong>, never optional</li>
<li>Route parameters will be merged into the <code class="notranslate">query</code> object (accessible from <code class="notranslate">getInitialProps</code> or <code class="notranslate">router</code> via <code class="notranslate">withRouter</code>) — these parameters can not be overridden by a query parameter</li>
</ol>
<p dir="auto">To help understand this proposal, let's examine the following file tree:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pages/
├── [root].js
├── blog/
│ └── [id].js
├── customers/
│ ├── [customer]/
│ │ ├── [post].js
│ │ ├── index.js
│ │ └── profile.js
│ ├── index.js
│ └── new.js
├── index.js
└── terms.js"><pre class="notranslate"><code class="notranslate">pages/
├── [root].js
├── blog/
│ └── [id].js
├── customers/
│ ├── [customer]/
│ │ ├── [post].js
│ │ ├── index.js
│ │ └── profile.js
│ ├── index.js
│ └── new.js
├── index.js
└── terms.js
</code></pre></div>
<p dir="auto">Next.js would produce the following routes, registered in the following order:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content=";[
  { 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' },
]"><pre class="notranslate"><span class="pl-kos">;</span><span class="pl-kos">[</span>
  <span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'/'</span><span class="pl-kos">,</span> <span class="pl-c1">page</span>: <span class="pl-s">'/index.js'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'/blog/:id'</span><span class="pl-kos">,</span> <span class="pl-c1">page</span>: <span class="pl-s">'/blog/[id].js'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'/customers'</span><span class="pl-kos">,</span> <span class="pl-c1">page</span>: <span class="pl-s">'/customers/index.js'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'/customers/new'</span><span class="pl-kos">,</span> <span class="pl-c1">page</span>: <span class="pl-s">'/customers/new.js'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'/customers/:customer'</span><span class="pl-kos">,</span> <span class="pl-c1">page</span>: <span class="pl-s">'/customers/[customer]/index.js'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-kos">{</span>
    <span class="pl-c1">path</span>: <span class="pl-s">'/customers/:customer/profile'</span><span class="pl-kos">,</span>
    <span class="pl-c1">page</span>: <span class="pl-s">'/customers/[customer]/profile.js'</span><span class="pl-kos">,</span>
  <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'/customers/:customer/:post'</span><span class="pl-kos">,</span> <span class="pl-c1">page</span>: <span class="pl-s">'/customers/[customer]/[post].js'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'/terms'</span><span class="pl-kos">,</span> <span class="pl-c1">page</span>: <span class="pl-s">'/terms.js'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-kos">{</span> <span class="pl-c1">path</span>: <span class="pl-s">'/:root'</span><span class="pl-kos">,</span> <span class="pl-c1">page</span>: <span class="pl-s">'/[root].js'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">]</span></pre></div>
<h2 dir="auto">Usage Examples</h2>
<p dir="auto">These examples all assume a page with the filename <code class="notranslate">pages/blog/[id].js</code>:</p>
<h3 dir="auto">Navigating to the Page with <code class="notranslate">&lt;Link /&gt;</code></h3>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&lt;Link href=&quot;/blog/[id]&quot; as=&quot;/blog/how-to-use-dynamic-routes&quot;&gt;
  &lt;a&gt;
    Next.js: Dynamic Routing{' '}
    &lt;span role=&quot;img&quot; aria-label=&quot;Party Popper&quot;&gt;
      🎉
    &lt;/span&gt;
  &lt;/a&gt;
&lt;/Link&gt;"><pre class="notranslate"><span class="pl-c1">&lt;</span><span class="pl-ent">Link</span> <span class="pl-c1">href</span><span class="pl-c1">=</span><span class="pl-s">"/blog/[id]"</span> <span class="pl-c1">as</span><span class="pl-c1">=</span><span class="pl-s">"/blog/how-to-use-dynamic-routes"</span><span class="pl-c1">&gt;</span>
  <span class="pl-c1">&lt;</span><span class="pl-ent">a</span><span class="pl-c1">&gt;</span>
    Next.js: Dynamic Routing<span class="pl-kos">{</span><span class="pl-s">' '</span><span class="pl-kos">}</span>
    <span class="pl-c1">&lt;</span><span class="pl-ent">span</span> <span class="pl-c1">role</span><span class="pl-c1">=</span><span class="pl-s">"img"</span> <span class="pl-c1">aria-label</span><span class="pl-c1">=</span><span class="pl-s">"Party Popper"</span><span class="pl-c1">&gt;</span>
      🎉
    <span class="pl-c1">&lt;</span><span class="pl-c1">/</span><span class="pl-ent">span</span><span class="pl-c1">&gt;</span>
  <span class="pl-c1">&lt;</span><span class="pl-c1">/</span><span class="pl-ent">a</span><span class="pl-c1">&gt;</span>
<span class="pl-c1">&lt;</span><span class="pl-c1">/</span><span class="pl-ent">Link</span><span class="pl-c1">&gt;</span></pre></div>
<p dir="auto">The above example will transition to the <code class="notranslate">/blog/[id].js</code> page and provide the following <code class="notranslate">query</code> object to the <em>Router</em>:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
  id: 'how-to-use-dynamic-routes'
}"><pre class="notranslate"><span class="pl-kos">{</span>
  <span class="pl-c1">id</span>: <span class="pl-s">'how-to-use-dynamic-routes'</span>
<span class="pl-kos">}</span></pre></div>
<h3 dir="auto">Reading Named Parameters from <em>Router</em></h3>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="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 &lt;main&gt;This is blog post {blogId}.&lt;/main&gt;
}

export default BlogPost"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">useRouter</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'next/router'</span>

<span class="pl-k">function</span> <span class="pl-v">BlogPost</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-k">const</span> <span class="pl-s1">router</span> <span class="pl-c1">=</span> <span class="pl-en">useRouter</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
  <span class="pl-c">// `blogId` will be `'how-to-use-dynamic-routes'` when rendering</span>
  <span class="pl-c">// `/blog/how-to-use-dynamic-routes`</span>
  <span class="pl-k">const</span> <span class="pl-s1">blogId</span> <span class="pl-c1">=</span> <span class="pl-s1">router</span><span class="pl-kos">.</span><span class="pl-c1">query</span><span class="pl-kos">.</span><span class="pl-c1">id</span>
  <span class="pl-k">return</span> <span class="pl-c1">&lt;</span><span class="pl-ent">main</span><span class="pl-c1">&gt;</span>This is blog post <span class="pl-kos">{</span><span class="pl-s1">blogId</span><span class="pl-kos">}</span>.<span class="pl-c1">&lt;</span><span class="pl-c1">/</span><span class="pl-ent">main</span><span class="pl-c1">&gt;</span>
<span class="pl-kos">}</span>

<span class="pl-k">export</span> <span class="pl-k">default</span> <span class="pl-v">BlogPost</span></pre></div>
<p dir="auto">Note: you can also use <code class="notranslate">withRouter</code>.</p>
<h3 dir="auto">Reading Named Parameters in <code class="notranslate">getInitialProps</code></h3>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="function BlogPost({ blogText }) {
  return &lt;main&gt;{blogText}&lt;/main&gt;
}

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 =&gt; res.json())

  return { blogText: text }
}

export default BlogPost"><pre class="notranslate"><span class="pl-k">function</span> <span class="pl-v">BlogPost</span><span class="pl-kos">(</span><span class="pl-kos">{</span> blogText <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-k">return</span> <span class="pl-c1">&lt;</span><span class="pl-ent">main</span><span class="pl-c1">&gt;</span><span class="pl-kos">{</span><span class="pl-s1">blogText</span><span class="pl-kos">}</span><span class="pl-c1">&lt;</span><span class="pl-c1">/</span><span class="pl-ent">main</span><span class="pl-c1">&gt;</span>
<span class="pl-kos">}</span>

<span class="pl-v">BlogPost</span><span class="pl-kos">.</span><span class="pl-en">getInitialProps</span> <span class="pl-c1">=</span> <span class="pl-k">async</span> <span class="pl-k">function</span><span class="pl-kos">(</span><span class="pl-kos">{</span> query <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-c">// `blogId` will be `'how-to-use-dynamic-routes'` when rendering</span>
  <span class="pl-c">// `/blog/how-to-use-dynamic-routes`</span>
  <span class="pl-k">const</span> <span class="pl-s1">blogId</span> <span class="pl-c1">=</span> <span class="pl-s1">query</span><span class="pl-kos">.</span><span class="pl-c1">id</span>

  <span class="pl-k">const</span> <span class="pl-kos">{</span> text <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">fetch</span><span class="pl-kos">(</span>
    <span class="pl-s">'/api/blog/content?id='</span> <span class="pl-c1">+</span> <span class="pl-en">encodeURIComponent</span><span class="pl-kos">(</span><span class="pl-s1">blogId</span><span class="pl-kos">)</span>
  <span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">then</span><span class="pl-kos">(</span><span class="pl-s1">res</span> <span class="pl-c1">=&gt;</span> <span class="pl-s1">res</span><span class="pl-kos">.</span><span class="pl-en">json</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span>

  <span class="pl-k">return</span> <span class="pl-kos">{</span> <span class="pl-c1">blogText</span>: <span class="pl-s1">text</span> <span class="pl-kos">}</span>
<span class="pl-kos">}</span>

<span class="pl-k">export</span> <span class="pl-k">default</span> <span class="pl-v">BlogPost</span></pre></div>
<h2 dir="auto">Caveats</h2>
<h3 dir="auto">Optional route parameters are not expressible through the filesystem.</h3>
<p dir="auto">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.</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// pages/blog/comments.js
// (the optional version of `pages/blog/[id]/comments.js`)
export { default } from './[id]/comments.js'"><pre class="notranslate"><span class="pl-c">// pages/blog/comments.js</span>
<span class="pl-c">// (the optional version of `pages/blog/[id]/comments.js`)</span>
<span class="pl-k">export</span> <span class="pl-kos">{</span> <span class="pl-s1">default</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'./[id]/comments.js'</span></pre></div>
<h3 dir="auto">Named parameters cannot appear in the middle of a route name.</h3>
<p dir="auto">This means a page named <code class="notranslate">blog-[id].js</code> would be interpreted <em>literally</em> and not matched by <code class="notranslate">/blog-1</code>. You can either restructure your page to be <code class="notranslate">/blog/[id].js</code> or turn the entire URL Segment into a named parameter and handle stripping <code class="notranslate">blog-</code> in your application's code.</p>
<h2 dir="auto">Alternatives</h2>
<h3 dir="auto">Denote URL Slugs with <em>insert symbol here</em> instead of <code class="notranslate">[]</code></h3>
<p dir="auto">There are <a href="https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words" rel="nofollow">very few symbols</a> available for use to represent a named parameter on the filesystem. Unfortunately, the most recognized way of defining a named parameter (<code class="notranslate">:name</code>) is <a href="https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words" rel="nofollow">not a valid filename</a>.</p>
<p dir="auto">While surveying prior art, the most common symbols used to denote a parameter were <code class="notranslate">_</code>, <code class="notranslate">$</code> and <code class="notranslate">[]</code>.</p>
<p dir="auto">We ruled out <code class="notranslate">_</code> because <code class="notranslate">_</code> is typically indicative of an <strong>internal route</strong> that is not publicly routable (e.g. <code class="notranslate">_app</code>, <code class="notranslate">_document</code>, <code class="notranslate">/_src</code>, <code class="notranslate">/_logs</code>).<br>
We also ruled out <code class="notranslate">$</code> because it is a sigil in bash for parameter expansion.</p>
<h3 dir="auto">Leverage <code class="notranslate">path-to-regexp</code> for comprehensive support</h3>
<p dir="auto">Most of the symbols required to express regex are <a href="https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words" rel="nofollow">not valid filenames</a>. Additionally, complex regexes are sensitive to route ordering for prioritization. The filesystem cannot express order nor contain regex symbols.</p>
<p dir="auto">In the future, we may allow <code class="notranslate">path-to-regexp</code> routes defined in <code class="notranslate">next.config.js</code> or similar. This is currently out of scope for this proposal.</p>
<h2 dir="auto">Future Exploration</h2>
<h3 dir="auto">Catch-All Parameters</h3>
<p dir="auto">In the future, we may consider adding catch-all parameters. With what we know thus far, these parameters must be at the <strong>end of the URL</strong> and would potentially use <code class="notranslate">%</code> to denote a catch-all route (e.g. <code class="notranslate">pages/website-builder/[customerName]/%.tsx</code>).</p>
<p dir="auto">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 <code class="notranslate">__NEXT_DATA__</code> and add it into the initial page load, when it is not needed for the initial display of the page?</p>
<p dir="auto">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.</p>
<p dir="auto">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.</p>
<p dir="auto">What do you think?</p>