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

Commit 479b7e7

Browse files
committed
Fix catch-all routes: Only match if at least one param is present
The default behavior of NextJS catch-all routes is to match only if at least one URL parameter is present. So far, next-on-netlify matched catch-all routes even when no URL parameter was present. For example, /pages/shows/[...slug] would match /shows/2, shows/3/my/path, but also /shows. This does not match the NextJS behavior described here: https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes This commit matches catch-all routes only when at least one URL parameter is present. It is in preparation for #15, which will introduce support for optional catch-all routes. Optional catch-all routes also match the page's base path without URL parameters (e.g. /shows).
1 parent 0412b45 commit 479b7e7

File tree

5 files changed

+123
-7
lines changed

5 files changed

+123
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Error from 'next/error'
2+
import Link from 'next/link'
3+
4+
const Show = ({ errorCode, show }) => {
5+
6+
// If show item was not found, render 404 page
7+
if (errorCode) {
8+
return <Error statusCode={errorCode} />
9+
}
10+
11+
// Otherwise, render show
12+
return (
13+
<div>
14+
<p>
15+
This page uses getInitialProps() to fetch the show with the ID
16+
provided in the URL: /shows/:id
17+
<br/>
18+
Refresh the page to see server-side rendering in action.
19+
<br/>
20+
You can also try changing the ID to any other number between 1-10000.
21+
</p>
22+
23+
<hr/>
24+
25+
<h1>Show #{show.id}</h1>
26+
<p>
27+
{show.name}
28+
</p>
29+
30+
<hr/>
31+
32+
<Link href="/">
33+
<a>Go back home</a>
34+
</Link>
35+
</div>
36+
)
37+
}
38+
39+
export const getServerSideProps = async ({ params }) => {
40+
// The ID to render
41+
const { slug } = params
42+
const id = slug[0]
43+
44+
const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
45+
const data = await res.json();
46+
47+
// Set error code if show item could not be found
48+
const errorCode = res.status > 200 ? res.status : false
49+
50+
return {
51+
props: {
52+
errorCode,
53+
show: data
54+
}
55+
}
56+
}
57+
58+
export default Show

cypress/fixtures/pages/index.js

+10
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ const Index = ({ shows }) => (
163163
<a>getServerSideProps/1338 (dynamic route)</a>
164164
</Link>
165165
</li>
166+
<li>
167+
<Link href="/getServerSideProps/catch/all/[...slug]" as="/getServerSideProps/catch/all/1337">
168+
<a>getServerSideProps/catch/all/1337 (catch-all route)</a>
169+
</Link>
170+
</li>
171+
<li>
172+
<Link href="/getServerSideProps/catch/all/[...slug]" as="/getServerSideProps/catch/all/1338">
173+
<a>getServerSideProps/catch/all/1338 (catch-all route)</a>
174+
</Link>
175+
</li>
166176
</ul>
167177

168178
<h1>6. Static Pages Stay Static</h1>

cypress/integration/default_spec.js

+50-2
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,14 @@ describe('getServerSideProps', () => {
153153

154154
context('with dynamic route', () => {
155155
it('loads TV show', () => {
156-
cy.visit('/shows/1337')
156+
cy.visit('/getServerSideProps/1337')
157157

158158
cy.get('h1').should('contain', 'Show #1337')
159159
cy.get('p').should('contain', 'Whodunnit?')
160160
})
161161

162162
it('loads TV show when SSR-ing', () => {
163-
cy.ssr('/shows/1337')
163+
cy.ssr('/getServerSideProps/1337')
164164

165165
cy.get('h1').should('contain', 'Show #1337')
166166
cy.get('p').should('contain', 'Whodunnit?')
@@ -186,6 +186,54 @@ describe('getServerSideProps', () => {
186186
cy.window().should('have.property', 'noReload', true)
187187
})
188188
})
189+
190+
context('with catch-all route', () => {
191+
it('does not match base path (without params)', () => {
192+
cy.request({
193+
url: '/getServerSideProps/catch/all',
194+
failOnStatusCode: false
195+
}).then(response => {
196+
expect(response.status).to.eq(404)
197+
cy.state('document').write(response.body)
198+
})
199+
200+
cy.get('h2').should('contain', 'This page could not be found.')
201+
})
202+
203+
it('loads TV show with one param', () => {
204+
cy.visit('/getServerSideProps/catch/all/1337')
205+
206+
cy.get('h1').should('contain', 'Show #1337')
207+
cy.get('p').should('contain', 'Whodunnit?')
208+
})
209+
210+
it('loads TV show with multiple params', () => {
211+
cy.visit('/getServerSideProps/catch/all/1337/multiple/params')
212+
213+
cy.get('h1').should('contain', 'Show #1337')
214+
cy.get('p').should('contain', 'Whodunnit?')
215+
})
216+
217+
it('loads page props from data .json file when navigating to it', () => {
218+
cy.visit('/')
219+
cy.window().then(w => w.noReload = true)
220+
221+
// Navigate to page and test that no reload is performed
222+
// See: https://glebbahmutov.com/blog/detect-page-reload/
223+
cy.contains('getServerSideProps/catch/all/1337').click()
224+
225+
cy.get('h1').should('contain', 'Show #1337')
226+
cy.get('p').should('contain', 'Whodunnit?')
227+
228+
cy.contains('Go back home').click()
229+
cy.contains('getServerSideProps/catch/all/1338').click()
230+
231+
cy.get('h1').should('contain', 'Show #1338')
232+
cy.get('p').should('contain', 'The Whole Truth')
233+
234+
cy.window().should('have.property', 'noReload', true)
235+
})
236+
})
189237
})
190238

191239
describe('getStaticProps', () => {

lib/getNetlifyRoute.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// also handles catch all routes /[...param]/ -> /:*
1010
module.exports = dynamicRoute => {
1111
// replace any catch all group first
12-
const expressified = dynamicRoute.replace(/\[\.\.\.(.*)](.json)?$/, "*");
12+
const expressified = dynamicRoute.replace(/\[\.\.\.(.*)](.json)?$/, ":$1/*");
1313

1414
// now replace other dynamic route groups
1515
return expressified.replace(/\[(.*?)]/g, ":$1");

tests/__snapshots__/defaults.test.js.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ exports[`Routing creates Netlify redirects 1`] = `
1717
/getStaticProps/withFallback/3 /getStaticProps/withFallback/3.html 200
1818
/getStaticProps/withFallback/4 /getStaticProps/withFallback/4.html 200
1919
/api/shows/:id /.netlify/functions/next_api_shows_id 200
20-
/api/shows/* /.netlify/functions/next_api_shows_params 200
20+
/api/shows/:params/* /.netlify/functions/next_api_shows_params 200
2121
/getServerSideProps/:id /.netlify/functions/next_getServerSideProps_id 200
2222
/_next/data/%BUILD_ID%/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200
2323
/getStaticProps/withFallback/:id /.netlify/functions/next_getStaticProps_withFallback_id 200
2424
/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200
25-
/getStaticProps/withFallback/* /.netlify/functions/next_getStaticProps_withFallback_slug 200
26-
/_next/data/%BUILD_ID%/getStaticProps/withFallback/* /.netlify/functions/next_getStaticProps_withFallback_slug 200
25+
/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200
26+
/_next/data/%BUILD_ID%/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200
2727
/shows/:id /.netlify/functions/next_shows_id 200
28-
/shows/* /.netlify/functions/next_shows_params 200
28+
/shows/:params/* /.netlify/functions/next_shows_params 200
2929
/static/:id /static/[id].html 200"
3030
`;

0 commit comments

Comments
 (0)