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

Commit 31b0f09

Browse files
committed
Add support for SSR pages using getServerSideProps
For pages that use getServerSideProps, we create a Netlify Function just like we do for pages with getInitialProps. In addition, we also add an extra redirect that allows getting the page props as JSON. This is when navigating to the page on the client-side. See: #7
1 parent 81026fb commit 31b0f09

File tree

9 files changed

+324
-14
lines changed

9 files changed

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

cypress/fixtures/pages/index.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,30 @@ const Index = ({ shows }) => (
127127
</li>
128128
</ul>
129129

130-
<h1>5. Static Pages Stay Static</h1>
130+
<h1>5. getServerSideProps? Yes!</h1>
131+
<p>
132+
next-on-netlify supports getServerSideProps.
133+
</p>
134+
135+
<ul>
136+
<li>
137+
<Link href="/getServerSideProps/static">
138+
<a>getServerSideProps/static</a>
139+
</Link>
140+
</li>
141+
<li>
142+
<Link href="/getServerSideProps/[id]" as="/getServerSideProps/1337">
143+
<a>getServerSideProps/1337 (dynamic route)</a>
144+
</Link>
145+
</li>
146+
<li>
147+
<Link href="/getServerSideProps/[id]" as="/getServerSideProps/1338">
148+
<a>getServerSideProps/1338 (dynamic route)</a>
149+
</Link>
150+
</li>
151+
</ul>
152+
153+
<h1>6. Static Pages Stay Static</h1>
131154
<p>
132155
next-on-netlify automatically determines which pages are dynamic and
133156
which ones are static.

cypress/integration/default_spec.js

+67-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ before(() => {
3737
cy.task('buildProject', { project })
3838

3939
// Deploy
40-
cy.task('deployProject', { project })
40+
cy.task('deployProject', { project }, { timeout: 180 * 1000 })
4141
})
4242

4343
// Set base URL
@@ -122,6 +122,72 @@ describe('getInitialProps', () => {
122122
})
123123
})
124124

125+
describe('getServerSideProps', () => {
126+
context('with static route', () => {
127+
it('loads TV shows', () => {
128+
cy.visit('/getServerSideProps/static')
129+
130+
cy.get('h1').should('contain', 'Show #42')
131+
cy.get('p').should('contain', 'Sleepy Hollow')
132+
})
133+
134+
it('loads TV shows when SSR-ing', () => {
135+
cy.ssr('/getServerSideProps/static')
136+
137+
cy.get('h1').should('contain', 'Show #42')
138+
cy.get('p').should('contain', 'Sleepy Hollow')
139+
})
140+
141+
it('loads page props from data .json file when navigating to it', () => {
142+
cy.visit('/')
143+
cy.window().then(w => w.noReload = true)
144+
145+
// Navigate to page and test that no reload is performed
146+
// See: https://glebbahmutov.com/blog/detect-page-reload/
147+
cy.contains('getServerSideProps/static').click()
148+
cy.get('h1').should('contain', 'Show #42')
149+
cy.get('p').should('contain', 'Sleepy Hollow')
150+
cy.window().should('have.property', 'noReload', true)
151+
})
152+
})
153+
154+
context('with dynamic route', () => {
155+
it('loads TV show', () => {
156+
cy.visit('/shows/1337')
157+
158+
cy.get('h1').should('contain', 'Show #1337')
159+
cy.get('p').should('contain', 'Whodunnit?')
160+
})
161+
162+
it('loads TV show when SSR-ing', () => {
163+
cy.ssr('/shows/1337')
164+
165+
cy.get('h1').should('contain', 'Show #1337')
166+
cy.get('p').should('contain', 'Whodunnit?')
167+
})
168+
169+
it('loads page props from data .json file when navigating to it', () => {
170+
cy.visit('/')
171+
cy.window().then(w => w.noReload = true)
172+
173+
// Navigate to page and test that no reload is performed
174+
// See: https://glebbahmutov.com/blog/detect-page-reload/
175+
cy.contains('getServerSideProps/1337').click()
176+
177+
cy.get('h1').should('contain', 'Show #1337')
178+
cy.get('p').should('contain', 'Whodunnit?')
179+
180+
cy.contains('Go back home').click()
181+
cy.contains('getServerSideProps/1338').click()
182+
183+
cy.get('h1').should('contain', 'Show #1338')
184+
cy.get('p').should('contain', 'The Whole Truth')
185+
186+
cy.window().should('have.property', 'noReload', true)
187+
})
188+
})
189+
})
190+
125191
describe('getStaticProps', () => {
126192
context('with static route', () => {
127193
it('loads TV show', () => {

lib/allNextJsPages.js

+25-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
const { join } = require('path')
2-
const { readJSONSync } = require('fs-extra')
3-
const { NEXT_DIST_DIR } = require('./config')
1+
const { join } = require('path')
2+
const { readFileSync, readJSONSync } = require('fs-extra')
3+
const { NEXT_DIST_DIR } = require('./config')
44

55
// Return all NextJS pages with route, file, type, and any other params
66
const getAllPages = () => {
@@ -16,6 +16,17 @@ const getAllPages = () => {
1616
join(NEXT_DIST_DIR, "prerender-manifest.json")
1717
)
1818

19+
// Read routes manifest that tells us which data routes should exist
20+
const routesManifest = readJSONSync(
21+
join(NEXT_DIST_DIR, "routes-manifest.json")
22+
)
23+
const dataRoutes = routesManifest.dataRoutes || []
24+
25+
// Get build ID that is used for data routes, e.g. /_next/data/BUILD_ID/...
26+
const fileContents = readFileSync(join(NEXT_DIST_DIR, "BUILD_ID"))
27+
const buildId = fileContents.toString()
28+
29+
1930
// Parse SSR and HTML pages
2031
Object.entries(ssrAndHtmlPages).forEach(([route, filePath]) => {
2132
// Skip framework pages, such as _app and _error
@@ -36,7 +47,17 @@ const getAllPages = () => {
3647

3748
// Otherwise, create new page
3849
const type = filePath.endsWith(".html") ? "html" : "ssr"
39-
pages.push(new Page({ route, type, filePath }))
50+
const alternativeRoutes = []
51+
52+
// Check if we have a data route for this page
53+
// This is relevant only for pages with getServerSideProps.
54+
// We need to add a second route for redirecting requests for
55+
// the JSON data to the Netlify Function.
56+
const dataRoute = dataRoutes.find(({ page }) => page === route)
57+
if(dataRoute)
58+
alternativeRoutes.push(join('/_next/data', buildId, `${route}.json`))
59+
60+
pages.push(new Page({ route, type, filePath, alternativeRoutes }))
4061
})
4162

4263
// Parse SSG pages

tests/__snapshots__/defaults.test.js.snap

+7-3
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33
exports[`Routing creates Netlify redirects 1`] = `
44
"# Next-on-Netlify Redirects
55
/api/static /.netlify/functions/next_api_static 200
6+
/getServerSideProps/static /.netlify/functions/next_getServerSideProps_static 200
7+
/_next/data/%BUILD_ID%/getServerSideProps/static.json /.netlify/functions/next_getServerSideProps_static 200
68
/index /.netlify/functions/next_index 200
79
/ /.netlify/functions/next_index 200
810
/static /static.html 200
911
/404 /404.html 200
12+
/getStaticProps/1 /getStaticProps/1.html 200
13+
/getStaticProps/2 /getStaticProps/2.html 200
1014
/getStaticProps/static /getStaticProps/static.html 200
1115
/getStaticProps/withFallback/3 /getStaticProps/withFallback/3.html 200
1216
/getStaticProps/withFallback/4 /getStaticProps/withFallback/4.html 200
13-
/getStaticProps/1 /getStaticProps/1.html 200
14-
/getStaticProps/2 /getStaticProps/2.html 200
1517
/api/shows/:id /.netlify/functions/next_api_shows_id 200
1618
/api/shows/* /.netlify/functions/next_api_shows_params 200
19+
/getServerSideProps/:id /.netlify/functions/next_getServerSideProps_id 200
20+
/_next/data/%BUILD_ID%/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200
1721
/getStaticProps/withFallback/:id /.netlify/functions/next_getStaticProps_withFallback_id 200
18-
/_next/data/$path$/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200
22+
/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200
1923
/shows/:id /.netlify/functions/next_shows_id 200
2024
/shows/* /.netlify/functions/next_shows_params 200
2125
/static/:id /static/[id].html 200"

tests/defaults.test.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ describe('SSR Pages', () => {
7777
const functionsDir = join(PROJECT_PATH, "out_functions")
7878

7979
test('creates a Netlify Function for each SSR page', () => {
80-
expect(existsSync(join(functionsDir, "next_index", "next_index.js"))).toBe(true)
81-
expect(existsSync(join(functionsDir, "next_shows_id", "next_shows_id.js"))).toBe(true)
82-
expect(existsSync(join(functionsDir, "next_shows_params", "next_shows_params.js"))).toBe(true)
80+
expect(existsSync(join(functionsDir, "next_index", "next_index.js"))).toBe(true)
81+
expect(existsSync(join(functionsDir, "next_shows_id", "next_shows_id.js"))).toBe(true)
82+
expect(existsSync(join(functionsDir, "next_shows_params", "next_shows_params.js"))).toBe(true)
83+
expect(existsSync(join(functionsDir, "next_getServerSideProps_static", "next_getServerSideProps_static.js"))).toBe(true)
84+
expect(existsSync(join(functionsDir, "next_getServerSideProps_id", "next_getServerSideProps_id.js"))).toBe(true)
8385
})
8486
})
8587

@@ -167,8 +169,8 @@ describe('Routing',() => {
167169
const contents = readFileSync(join(PROJECT_PATH, "out_publish", "_redirects"))
168170
let redirects = contents.toString()
169171

170-
// Remove build-specific data path
171-
redirects = redirects.replace(/\/_next\/data\/[^\/]+\//, "/_next/data/$path$/")
172+
// Replace non-persistent build ID with placeholder
173+
redirects = redirects.replace(/\/_next\/data\/[^\/]+\//g, "/_next/data/%BUILD_ID%/")
172174

173175
// Check that redirects match
174176
expect(redirects).toMatchSnapshot()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 { id } = params
42+
43+
const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
44+
const data = await res.json();
45+
46+
// Set error code if show item could not be found
47+
const errorCode = res.status > 200 ? res.status : false
48+
49+
return {
50+
props: {
51+
errorCode,
52+
show: data
53+
}
54+
}
55+
}
56+
57+
export default Show

0 commit comments

Comments
 (0)