Skip to content

Commit 7822aed

Browse files
feat: preview mode (#16)
* feat: preview mode * chore: fix snapshot * chore: fix snapshot.. again? * fix: use distDir var when reading prerender-manifest, ot .next * chore: add redirects sort back in..? * fix: redirects sorting
1 parent cb6c987 commit 7822aed

File tree

10 files changed

+188
-41
lines changed

10 files changed

+188
-41
lines changed

demo/next.config.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
module.exports = {
2-
target: 'serverless',
2+
generateBuildId: () => 'build',
3+
// Configurable site features to support:
4+
// i18n: {
5+
// defaultLocale: 'en',
6+
// locales: ['en', 'es', 'fr']
7+
// },
8+
// basePath: '/docs',
9+
// distDir: 'build',
310
}

demo/pages/api/enterPreview.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default async function preview(req, res) {
2+
// Enable Preview Mode by setting the cookies
3+
res.setPreviewData({})
4+
5+
res.status(200).json({ name: 'preview mode' })
6+
}

demo/pages/api/exitPreview.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default async function exit(_, res) {
2+
// Exit the current user from "Preview Mode". This function accepts no args.
3+
res.clearPreviewData()
4+
5+
// Redirect the user back to the index page.
6+
res.writeHead(307, { Location: '/' })
7+
res.end()
8+
}

demo/pages/previewTest.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import Link from 'next/link'
2+
3+
const StaticTest = ({ number }) => {
4+
return (
5+
<div>
6+
<p>
7+
This page uses getStaticProps() and is SSRed when in preview mode.
8+
<br />
9+
<br />
10+
By default, it shows the TV show by ID (as static HTML).
11+
<br />
12+
But when in preview mode, it shows person by ID instead (SSRed).
13+
</p>
14+
15+
<hr />
16+
17+
<h1>Number: {number}</h1>
18+
19+
<Link href="/">
20+
<a>Go back home</a>
21+
</Link>
22+
</div>
23+
)
24+
}
25+
26+
export const getStaticProps = async ({ preview }) => {
27+
let number
28+
29+
// In preview mode, use odd number
30+
if (preview) {
31+
number = 3
32+
}
33+
// In normal mode, use even number
34+
else {
35+
number = 4
36+
}
37+
38+
return {
39+
props: {
40+
number,
41+
},
42+
}
43+
}
44+
45+
export default StaticTest

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"publish:pull": "git pull",
2323
"publish:install": "npm ci",
2424
"publish:test": "npm test",
25-
"test": "run-s build:demo test:jest",
25+
"test": "run-s build build:demo test:jest",
2626
"test:jest": "jest",
2727
"prepare": "npm run build",
2828
"clean": "rimraf lib",

src/helpers/getHandler.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,31 @@ const makeHandler =
2727
bridge.listen()
2828

2929
return async (event, context) => {
30-
const result = await bridge.launcher(event, context)
30+
const { headers, ...result } = await bridge.launcher(event, context)
3131
/** @type import("@netlify/functions").HandlerResponse */
32+
33+
// Convert all headers to multiValueHeaders
34+
const multiValueHeaders = {}
35+
for (const key of Object.keys(headers)) {
36+
if (Array.isArray(headers[key])) {
37+
multiValueHeaders[key] = headers[key]
38+
} else {
39+
multiValueHeaders[key] = [headers[key]]
40+
}
41+
}
42+
43+
if (
44+
multiValueHeaders['set-cookie'] &&
45+
multiValueHeaders['set-cookie'][0] &&
46+
multiValueHeaders['set-cookie'][0].includes('__prerender_bypass')
47+
) {
48+
delete multiValueHeaders.etag
49+
multiValueHeaders['cache-control'] = ['no-cache']
50+
}
51+
3252
return {
3353
...result,
54+
multiValueHeaders,
3455
isBase64Encoded: result.encoding === 'base64',
3556
}
3657
}

src/helpers/writeRedirects.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,38 +45,39 @@ const getNetlifyRoutes = (nextRoute) => {
4545
return netlifyRoutes
4646
}
4747

48-
const writeRedirects = async ({ siteRoot = process.cwd(), netlifyConfig }) => {
49-
const { dynamicRoutes } = await readJSON(path.join(siteRoot, '.next', 'prerender-manifest.json'))
48+
const writeRedirects = async ({ siteRoot = process.cwd(), distDir, netlifyConfig }) => {
49+
const { dynamicRoutes } = await readJSON(path.join(siteRoot, distDir, 'prerender-manifest.json'))
5050

5151
const redirects = []
52-
Object.entries(dynamicRoutes).forEach(([route, { dataRoute, fallback }]) => {
53-
// We want to add redirects if the fallback is "null" or true
52+
53+
const dynamicRouteEntries = Object.entries(dynamicRoutes)
54+
dynamicRouteEntries.sort((a, b) => a[0].localeCompare(b[0]))
55+
56+
dynamicRouteEntries.forEach(([route, { dataRoute, fallback }]) => {
57+
// Add redirects if fallback is "null" (aka blocking) or true/a string
5458
if (fallback === false) {
5559
return
5660
}
5761
redirects.push(...getNetlifyRoutes(route), ...getNetlifyRoutes(dataRoute))
5862
})
59-
redirects.sort()
6063

6164
// This is only used in prod, so dev uses `next dev` directly
6265
netlifyConfig.redirects.push(
66+
{ from: '/_next/static/*', to: '/static/:splat', status: 200 },
67+
{
68+
from: '/*',
69+
to: HANDLER_FUNCTION_PATH,
70+
status: 200,
71+
force: true,
72+
conditions: { Cookie: ['__prerender_bypass', '__next_preview_data'] },
73+
},
6374
...redirects.map((redirect) => ({
6475
from: redirect,
6576
to: ODB_FUNCTION_PATH,
6677
status: 200,
6778
})),
68-
{ from: '/_next/static/*', to: '/static/:splat', status: 200 },
6979
{ from: '/*', to: HANDLER_FUNCTION_PATH, status: 200 },
7080
)
71-
// If we want to use the Netlify functions handler we'd need to do it like this,
72-
// as `ntl dev` doesn't support mutating redirects at the moment. Maybe this should be an env var, to make testing easier?
73-
// const odbRedirects = `${redirects
74-
// .map((redir) => `${redir} ${ODB_FUNCTION_PATH} 200`)
75-
// .join("\n")}
76-
// /_next/static/* /static/:splat 200
77-
// /* ${HANDLER_FUNCTION_PATH} 200
78-
// `;
79-
// await writeFile(path.join(siteRoot, publishDir, "_redirects"), odbRedirects);
8081
}
8182

8283
module.exports = writeRedirects

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,13 @@ module.exports = {
5858

5959
const siteRoot = getNextRoot({ netlifyConfig })
6060

61-
const { images } = await getNextConfig(failBuild, siteRoot)
61+
const { distDir, images } = await getNextConfig(failBuild, siteRoot)
6262

6363
await setupImageFunction({ constants, imageconfig: images, netlifyConfig })
6464

6565
await writeRedirects({
6666
siteRoot,
67+
distDir,
6768
netlifyConfig,
6869
})
6970

test/__snapshots__/index.js.snap

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,83 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`onBuild() writes correct redirects to netlifyConfig 1`] = `
4-
"/_next/image* /_ipx/w_:width,q_:quality/:url 301
5-
/_ipx/* /.netlify/functions/_ipx 200
6-
/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/___netlify-odb-handler 200
7-
/_next/data/%BUILD_ID%/getStaticProps/withFallback/:slug/* /.netlify/functions/___netlify-odb-handler 200
8-
/_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/___netlify-odb-handler 200
9-
/_next/data/%BUILD_ID%/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/___netlify-odb-handler 200
10-
/getStaticProps/withFallback/:id /.netlify/functions/___netlify-odb-handler 200
11-
/getStaticProps/withFallback/:slug/* /.netlify/functions/___netlify-odb-handler 200
12-
/getStaticProps/withFallbackBlocking/:id /.netlify/functions/___netlify-odb-handler 200
13-
/getStaticProps/withRevalidate/withFallback/:id /.netlify/functions/___netlify-odb-handler 200
14-
/_next/static/* /static/:splat 200
15-
/* /.netlify/functions/___netlify-handler 200
16-
"
4+
Array [
5+
Object {
6+
"from": "/_next/image*",
7+
"query": Object {
8+
"q": ":quality",
9+
"url": ":url",
10+
"w": ":width",
11+
},
12+
"status": 301,
13+
"to": "/_ipx/w_:width,q_:quality/:url",
14+
},
15+
Object {
16+
"from": "/_ipx/*",
17+
"status": 200,
18+
"to": "/.netlify/functions/_ipx",
19+
},
20+
Object {
21+
"from": "/_next/static/*",
22+
"status": 200,
23+
"to": "/static/:splat",
24+
},
25+
Object {
26+
"conditions": Object {
27+
"Cookie": Array [
28+
"__prerender_bypass",
29+
"__next_preview_data",
30+
],
31+
},
32+
"force": true,
33+
"from": "/*",
34+
"status": 200,
35+
"to": "/.netlify/functions/___netlify-handler",
36+
},
37+
Object {
38+
"from": "/getStaticProps/withFallback/:slug/*",
39+
"status": 200,
40+
"to": "/.netlify/functions/___netlify-odb-handler",
41+
},
42+
Object {
43+
"from": "/_next/data/build/getStaticProps/withFallback/:slug/*",
44+
"status": 200,
45+
"to": "/.netlify/functions/___netlify-odb-handler",
46+
},
47+
Object {
48+
"from": "/getStaticProps/withFallback/:id",
49+
"status": 200,
50+
"to": "/.netlify/functions/___netlify-odb-handler",
51+
},
52+
Object {
53+
"from": "/_next/data/build/getStaticProps/withFallback/:id.json",
54+
"status": 200,
55+
"to": "/.netlify/functions/___netlify-odb-handler",
56+
},
57+
Object {
58+
"from": "/getStaticProps/withFallbackBlocking/:id",
59+
"status": 200,
60+
"to": "/.netlify/functions/___netlify-odb-handler",
61+
},
62+
Object {
63+
"from": "/_next/data/build/getStaticProps/withFallbackBlocking/:id.json",
64+
"status": 200,
65+
"to": "/.netlify/functions/___netlify-odb-handler",
66+
},
67+
Object {
68+
"from": "/getStaticProps/withRevalidate/withFallback/:id",
69+
"status": 200,
70+
"to": "/.netlify/functions/___netlify-odb-handler",
71+
},
72+
Object {
73+
"from": "/_next/data/build/getStaticProps/withRevalidate/withFallback/:id.json",
74+
"status": 200,
75+
"to": "/.netlify/functions/___netlify-odb-handler",
76+
},
77+
Object {
78+
"from": "/*",
79+
"status": 200,
80+
"to": "/.netlify/functions/___netlify-handler",
81+
},
82+
]
1783
`;

test/index.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -317,15 +317,7 @@ describe('onBuild()', () => {
317317
utils,
318318
})
319319

320-
let redirects = netlifyConfig.redirects.reduce((acc, curr) => {
321-
const { from, to, status } = curr
322-
return acc + `${from} ${to} ${status}\n`
323-
}, '')
324-
325-
// Replace non-persistent build ID with placeholder
326-
redirects = redirects.replace(/\/_next\/data\/[^\/]+\//g, '/_next/data/%BUILD_ID%/')
327-
328-
expect(redirects).toMatchSnapshot()
320+
expect(netlifyConfig.redirects).toMatchSnapshot()
329321
})
330322

331323
test('publish dir is/has next dist', async () => {

0 commit comments

Comments
 (0)