Skip to content
This repository was archived by the owner on May 10, 2021. It is now read-only.

You cannot define a route with the same specificity as a optional catch-all route #76

Closed
afzalsayed96 opened this issue Nov 13, 2020 · 6 comments · Fixed by #145
Closed
Labels
priority: medium type: bug code to address defects in shipped code

Comments

@afzalsayed96
Copy link
Contributor

afzalsayed96 commented Nov 13, 2020

I have the following page structure:

Page                              
┌   /_app                       
├ ● /[[...rest]]               
├   ├ /
├   └ /careers
├ ○ /404     
└ λ /api/preview                                     

Command build runs successfully but running next-on-netlify spits out the following error:

Error: You cannot define a route with the same specificity as a optional catch-all route ("/" and "/[[...rest]]").
@FinnWoelm
Copy link
Contributor

Hi @afzalsayed96,

Welcome to next-on-netlify! 👋

Thanks for the detailed error description and page structure. The error you receive is intentional: You have an index route /index.js and an optional-catch all route (/[[...rest]].js) which are in conflict with one another because both map onto requests for /.

If you want the index route to render on requests for /, change the optional catch-all route to a normal (non-optional) catch-all route: [...rest]. This will match anything but the index route /. https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes

If you want the optional catch all route to render on requests for /, simply delete index.js and the error will go away.

Does this help? Please let us know if you have any doubts at all and I'm more than happy to expand on this further! 😊

@afzalsayed96
Copy link
Contributor Author

afzalsayed96 commented Nov 14, 2020

Hey @FinnWoelm,

I do not have an index page explicitly defined. My pages directory looks like this.

pages
  _app.js
 [[...rest]].js

Instead getStaticPaths inside [[...rest]].js returns 2 paths '/' and '/careers'. Going by the docs and this comment, I feel this is a valid case.

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

Similarly I need pages/[[...rest]] to match / and /careers

Also note that there's no error on next dev and next build. The issue I'm encountering is occurring only on running next-on-netlify and I believe that it's a bug.

@lindsaylevine
Copy link
Contributor

hey @afzalsayed96 i hate to ask but can you open a repro repo for us 🙏

@afzalsayed96
Copy link
Contributor Author

https://github.com/afzalsayed96/netlify-nextjs-repro
Here you go @lindsaylevine

Also here's full error log:
❯ yarn build
yarn run v1.22.5
$ next build
Page                                                           Size     First Load JS
┌   /_app                                                      0 B            61.4 kB
├ ● /[[...rest]]                                               232 B          61.6 kB
├   ├ /
├   └ /careers
└ ○ /404                                                       3.44 kB        64.9 kB
+ First Load JS shared by all                                  61.4 kB
  ├ chunks/f6078781a05fe1bcb0902d23dbbb2662c8d200b3.1cb026.js  11.5 kB
  ├ chunks/framework.964e76.js                                 39.9 kB
  ├ chunks/main.7bf6ce.js                                      6.99 kB
  ├ chunks/pages/_app.faee11.js                                2.32 kB
  └ chunks/webpack.e06743.js                                   751 B

λ  (Lambda)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)
●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
   (ISR)     incremental static regeneration (uses revalidate in getStaticProps)

$ next-on-netlify
🚀 Next on Netlify 🚀
   Functions directory: out_functions/
   Publish directory:   out_publish/
   Make sure these are set in your netlify.toml file.
🌍️ Copying public/ folder to out_publish/
💼 Copying static NextJS assets to out_publish/
💫 Setting up API endpoints as Netlify Functions in out_functions/
💫 Setting up pages with getInitialProps as Netlify Functions in out_functions/
💫 Setting up pages with getServerSideProps as Netlify Functions in out_functions/
🔥 Copying pre-rendered pages with getStaticProps and JSON data to out_publish/
   /
   /careers
💫 Setting up pages with getStaticProps and fallback: true as Netlify Functions in out_functions/
   pages/[[...rest]].js
💫 Setting up pages with getStaticProps and revalidation interval as Netlify Functions in out_functions/
🔥 Copying pre-rendered pages without props to out_publish/
   pages/404.html
🔀 Setting up redirects
/Users/afzal/projects/keepworks-site/node_modules/next/dist/next-server/lib/router/utils/sorted-routes.js:1
"use strict";exports.__esModule=true;exports.getSortedRoutes=getSortedRoutes;class UrlNode{constructor(){this.placeholder=true;this.children=new Map();this.slugName=null;this.restSlugName=null;this.optionalRestSlugName=null;}insert(urlPath){this._insert(urlPath.split('/').filter(Boolean),[],false);}smoosh(){return this._smoosh();}_smoosh(prefix='/'){const childrenPaths=[...this.children.keys()].sort();if(this.slugName!==null){childrenPaths.splice(childrenPaths.indexOf('[]'),1);}if(this.restSlugName!==null){childrenPaths.splice(childrenPaths.indexOf('[...]'),1);}if(this.optionalRestSlugName!==null){childrenPaths.splice(childrenPaths.indexOf('[[...]]'),1);}const routes=childrenPaths.map(c=>this.children.get(c)._smoosh(`${prefix}${c}/`)).reduce((prev,curr)=>[...prev,...curr],[]);if(this.slugName!==null){routes.push(...this.children.get('[]')._smoosh(`${prefix}[${this.slugName}]/`));}if(!this.placeholder){const r=prefix==='/'?'/':prefix.slice(0,-1);if(this.optionalRestSlugName!=null){throw new Error(`You cannot define a route with the same specificity as a optional catch-all route ("${r}" and "${r}[[...${this.optionalRestSlugName}]]").`);}routes.unshift(r);}if(this.restSlugName!==null){routes.push(...this.children.get('[...]')._smoosh(`${prefix}[...${this.restSlugName}]/`));}if(this.optionalRestSlugName!==null){routes.push(...this.children.get('[[...]]')._smoosh(`${prefix}[[...${this.optionalRestSlugName}]]/`));}return routes;}_insert(urlPaths,slugNames,isCatchAll){if(urlPaths.length===0){this.placeholder=false;return;}if(isCatchAll){throw new Error(`Catch-all must be the last part of the URL.`);}// The next segment in the urlPaths list
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   ^

Error: You cannot define a route with the same specificity as a optional catch-all route ("/" and "/[[...rest]]").
    at UrlNode._smoosh (/Users/afzal/projects/keepworks-site/node_modules/next/dist/next-server/lib/router/utils/sorted-routes.js:1:1002)
    at UrlNode.smoosh (/Users/afzal/projects/keepworks-site/node_modules/next/dist/next-server/lib/router/utils/sorted-routes.js:1:322)
    at getSortedRoutes (/Users/afzal/projects/keepworks-site/node_modules/next/dist/next-server/lib/router/utils/sorted-routes.js:31:13)
    at getSortedRoutes (/Users/afzal/projects/keepworks-site/node_modules/next-on-netlify/lib/helpers/getSortedRoutes.js:19:24)
    at setupRedirects (/Users/afzal/projects/keepworks-site/node_modules/next-on-netlify/lib/steps/setupRedirects.js:37:24)
    at nextOnNetlify (/Users/afzal/projects/keepworks-site/node_modules/next-on-netlify/index.js:16:3)
    at Object.<anonymous> (/Users/afzal/projects/keepworks-site/node_modules/next-on-netlify/next-on-netlify.js:15:1)
    at Module._compile (internal/modules/cjs/loader.js:1200:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1220:10)
    at Module.load (internal/modules/cjs/loader.js:1049:32)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

I realized that this happens only when fallback is set to true.

@lindsaylevine lindsaylevine added the type: bug code to address defects in shipped code label Dec 3, 2020
@FinnWoelm
Copy link
Contributor

Hi @afzalsayed96,

Thank you for creating this repo for reproducing the issue! That was fantastic and extremely helpful.

I'm happy to report that @lindsaylevine and I have identified the issue. It's indeed a bug in next-on-netlify, so thank you so much for catching and reporting that.

The issue is related to the way in which we sort the redirects that next-on-netlify creates under the hood. In the case of the sample repo you shared, we create six redirects:

[
  {
    route: '/',
    target: '/.netlify/functions/next_rest',
    force: true,
    conditions: [ 'Cookie=__prerender_bypass,__next_preview_data' ]
  },
  {
    route: '/_next/data/xvcNJopdBjVWAhGZJIQ9K/index.json',
    target: '/.netlify/functions/next_rest',
    force: true,
    conditions: [ 'Cookie=__prerender_bypass,__next_preview_data' ]
  },
  {
    route: '/careers',
    target: '/.netlify/functions/next_rest',
    force: true,
    conditions: [ 'Cookie=__prerender_bypass,__next_preview_data' ]
  },
  {
    route: '/_next/data/xvcNJopdBjVWAhGZJIQ9K/careers.json',
    target: '/.netlify/functions/next_rest',
    force: true,
    conditions: [ 'Cookie=__prerender_bypass,__next_preview_data' ]
  },
  { route: '/[[...rest]]', target: '/.netlify/functions/next_rest' },
  {
    route: '/_next/data/xvcNJopdBjVWAhGZJIQ9K/[[...rest]].json',
    target: '/.netlify/functions/next_rest'
  }
]

We use a Next.js-internal sorting function to ensure that our redirects are put in the right order (more specific, static routes must come before more generic, dynamic routes).

For example, the redirect for /careers must precede the redirect for //[[...rest]]. It turns out that with the redirects that we add to support Next.js preview mode, we have a few edge cases where the Next.js-internal sorting function unintentionally reports an error, even though there is none.

That's not a mistake on Next.js' side: the Next.js function was not intended to sort redirects like ours; we have just been "misusing" it because it worked well — until now :)

We will fix this by returning our sorting strategy back to what we had back in the day, where we let Next.js sort only our dynamic redirects (e.g., [slug].js or [[...rest]].js), but not our static redirects:

// Identify static and dynamically routed pages

With the holidays around the corner, we'll probably need a bit to get this fixed (early next year?!). We will also add a regression test for this scenario, to make sure that it does not happen again in the future!

We will keep you posted! Thank you again @afzalsayed96 😊

@lindsaylevine
Copy link
Contributor

hey @afzalsayed96 ! just merged the sorting work and confirmed NoN runs on the repo you provided :) will release shortly

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
priority: medium type: bug code to address defects in shipped code
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants