Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add proper error when conflicting paths are detected (#20918)
This helps catch conflicting paths returned from `getStaticPaths` with a friendly error <details> <summary> Preview of error </summary> <img width="962" alt="Screen Shot 2021-01-08 at 5 03 04 PM" src="https://user-images.githubusercontent.com/22380829/104074719-6e481100-51d6-11eb-9397-938aee3ae30b.png"> <img width="962" alt="Screen Shot 2021-01-08 at 5 03 41 PM" src="https://user-images.githubusercontent.com/22380829/104074722-6f793e00-51d6-11eb-90f6-7cdf9882bf00.png"> </details> Closes: #19527
- Loading branch information
Showing
4 changed files
with
338 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Conflicting SSG Paths | ||
|
||
#### Why This Error Occurred | ||
|
||
In your `getStaticPaths` function for one of your pages you returned conflicting paths. All page paths must be unique and duplicates are not allowed. | ||
|
||
#### Possible Ways to Fix It | ||
|
||
Remove any conflicting paths shown in the error message and only return them from one `getStaticPaths`. | ||
|
||
Example conflicting paths: | ||
|
||
```jsx | ||
// pages/hello/world.js | ||
export default function Hello() { | ||
return 'hello world!' | ||
} | ||
|
||
// pages/[...catchAll].js | ||
export const getStaticProps = () => ({ props: {} }) | ||
|
||
export const getStaticPaths = () => ({ | ||
paths: [ | ||
// this conflicts with the /hello/world.js page, remove to resolve error | ||
'/hello/world', | ||
'/another', | ||
], | ||
fallback: false, | ||
}) | ||
|
||
export default function CatchAll() { | ||
return 'Catch-all page' | ||
} | ||
``` | ||
|
||
Example conflicting paths: | ||
|
||
```jsx | ||
// pages/blog/[slug].js | ||
export const getStaticPaths = () => ({ | ||
paths: ['/blog/conflicting', '/blog/another'], | ||
fallback: false, | ||
}) | ||
|
||
export default function Blog() { | ||
return 'Blog!' | ||
} | ||
|
||
// pages/[...catchAll].js | ||
export const getStaticProps = () => ({ props: {} }) | ||
|
||
export const getStaticPaths = () => ({ | ||
paths: [ | ||
// this conflicts with the /blog/conflicting path above, remove to resolve error | ||
'/blog/conflicting', | ||
'/another', | ||
], | ||
fallback: false, | ||
}) | ||
|
||
export default function CatchAll() { | ||
return 'Catch-all page' | ||
} | ||
``` | ||
|
||
### Useful Links | ||
|
||
- [`getStaticPaths` Documentation](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
183 changes: 183 additions & 0 deletions
183
test/integration/conflicting-ssg-paths/test/index.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
/* eslint-env jest */ | ||
|
||
import fs from 'fs-extra' | ||
import { join } from 'path' | ||
import { nextBuild } from 'next-test-utils' | ||
|
||
jest.setTimeout(1000 * 60 * 1) | ||
|
||
const appDir = join(__dirname, '../') | ||
const pagesDir = join(appDir, 'pages') | ||
|
||
describe('Conflicting SSG paths', () => { | ||
afterEach(() => fs.remove(pagesDir)) | ||
|
||
it('should show proper error when two dynamic SSG routes have conflicting paths', async () => { | ||
await fs.ensureDir(join(pagesDir, 'blog')) | ||
await fs.writeFile( | ||
join(pagesDir, 'blog/[slug].js'), | ||
` | ||
export const getStaticProps = () => { | ||
return { | ||
props: {} | ||
} | ||
} | ||
export const getStaticPaths = () => { | ||
return { | ||
paths: [ | ||
'/blog/conflicting', | ||
'/blog/first' | ||
], | ||
fallback: false | ||
} | ||
} | ||
export default function Page() { | ||
return '/blog/[slug]' | ||
} | ||
` | ||
) | ||
|
||
await fs.writeFile( | ||
join(pagesDir, '[...catchAll].js'), | ||
` | ||
export const getStaticProps = () => { | ||
return { | ||
props: {} | ||
} | ||
} | ||
export const getStaticPaths = () => { | ||
return { | ||
paths: [ | ||
'/blog/conflicting', | ||
'/hello/world' | ||
], | ||
fallback: false | ||
} | ||
} | ||
export default function Page() { | ||
return '/[catchAll]' | ||
} | ||
` | ||
) | ||
|
||
const result = await nextBuild(appDir, undefined, { | ||
stdout: true, | ||
stderr: true, | ||
}) | ||
const output = result.stdout + result.stderr | ||
expect(output).toContain( | ||
'Conflicting paths returned from getStaticPaths, paths must unique per page' | ||
) | ||
expect(output).toContain('err.sh/next.js/conflicting-ssg-paths') | ||
expect(output).toContain( | ||
`path: "/blog/conflicting" from page: "/[...catchAll]"` | ||
) | ||
expect(output).toContain(`conflicts with path: "/blog/conflicting"`) | ||
}) | ||
|
||
it('should show proper error when a dynamic SSG route conflicts with normal route', async () => { | ||
await fs.ensureDir(join(pagesDir, 'hello')) | ||
await fs.writeFile( | ||
join(pagesDir, 'hello/world.js'), | ||
` | ||
export default function Page() { | ||
return '/hello/world' | ||
} | ||
` | ||
) | ||
|
||
await fs.writeFile( | ||
join(pagesDir, '[...catchAll].js'), | ||
` | ||
export const getStaticProps = () => { | ||
return { | ||
props: {} | ||
} | ||
} | ||
export const getStaticPaths = () => { | ||
return { | ||
paths: [ | ||
'/hello', | ||
'/hellO/world' | ||
], | ||
fallback: false | ||
} | ||
} | ||
export default function Page() { | ||
return '/[catchAll]' | ||
} | ||
` | ||
) | ||
|
||
const result = await nextBuild(appDir, undefined, { | ||
stdout: true, | ||
stderr: true, | ||
}) | ||
const output = result.stdout + result.stderr | ||
expect(output).toContain( | ||
'Conflicting paths returned from getStaticPaths, paths must unique per page' | ||
) | ||
expect(output).toContain('err.sh/next.js/conflicting-ssg-paths') | ||
expect(output).toContain( | ||
`path: "/hellO/world" from page: "/[...catchAll]" conflicts with path: "/hello/world"` | ||
) | ||
}) | ||
|
||
it('should show proper error when a dynamic SSG route conflicts with SSR route', async () => { | ||
await fs.ensureDir(join(pagesDir, 'hello')) | ||
await fs.writeFile( | ||
join(pagesDir, 'hello/world.js'), | ||
` | ||
export const getServerSideProps = () => ({ props: {} }) | ||
export default function Page() { | ||
return '/hello/world' | ||
} | ||
` | ||
) | ||
|
||
await fs.writeFile( | ||
join(pagesDir, '[...catchAll].js'), | ||
` | ||
export const getStaticProps = () => { | ||
return { | ||
props: {} | ||
} | ||
} | ||
export const getStaticPaths = () => { | ||
return { | ||
paths: [ | ||
'/hello', | ||
'/hellO/world' | ||
], | ||
fallback: false | ||
} | ||
} | ||
export default function Page() { | ||
return '/[catchAll]' | ||
} | ||
` | ||
) | ||
|
||
const result = await nextBuild(appDir, undefined, { | ||
stdout: true, | ||
stderr: true, | ||
}) | ||
const output = result.stdout + result.stderr | ||
expect(output).toContain( | ||
'Conflicting paths returned from getStaticPaths, paths must unique per page' | ||
) | ||
expect(output).toContain('err.sh/next.js/conflicting-ssg-paths') | ||
expect(output).toContain( | ||
`path: "/hellO/world" from page: "/[...catchAll]" conflicts with path: "/hello/world"` | ||
) | ||
}) | ||
}) |