Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c0b655f

Browse files
committedJun 1, 2021
feat: support for basePath
1 parent cfaa517 commit c0b655f

File tree

10 files changed

+964
-7
lines changed

10 files changed

+964
-7
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
target: "experimental-serverless-trace",
3+
basePath: "/foo",
4+
};

‎src/cypress/fixtures/pages/getServerSideProps/[id].js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const Show = ({ errorCode, show }) => {
1111
return (
1212
<div>
1313
<p>
14-
This page uses getInitialProps() to fetch the show with the ID provided in the URL: /shows/:id
14+
This page uses getServerSideProps() to fetch the show with the ID provided in the URL: /shows/:id
1515
<br />
1616
Refresh the page to see server-side rendering in action.
1717
<br />

‎src/cypress/fixtures/pages/getServerSideProps/static.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Link from 'next/link'
33
const Show = ({ show }) => (
44
<div>
55
<p>
6-
This page uses getInitialProps() to fetch the show with the ID provided in the URL: /shows/:id
6+
This page uses getServerSideProps() to fetch the show with the ID provided in the URL: /shows/:id
77
<br />
88
Refresh the page to see server-side rendering in action.
99
<br />

‎src/cypress/integration/basePath_spec.js

+735
Large diffs are not rendered by default.

‎src/lib/config.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const { join } = require('path')
22

3-
const getNextDistDir = require('./helpers/getNextDistDir')
43
const getNextSrcDirs = require('./helpers/getNextSrcDir')
54

65
// This is where next-on-netlify will place all static files.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// This helper converts the collection of redirects for all page types into
2+
// the necessary redirects for a basePath-generated site
3+
// i.e.
4+
// no basePath:
5+
// /ssr /.netlify/functions/next_ssr 200
6+
// with basePath configured:
7+
// /ssr /base/ssr 301!
8+
// /base/ssr /.netlify/functions/next_ssr 200
9+
10+
const getBasePathDefaultRedirects = ({ basePath, nextRedirects }) => {
11+
if (basePath === '') return []
12+
return [
13+
// {
14+
// route: `${basePath}*`,
15+
// target: '/:splat'
16+
// },
17+
{
18+
route: `${basePath}/_next/*`,
19+
target: '/_next/:splat',
20+
statusCode: '301',
21+
force: true,
22+
},
23+
]
24+
}
25+
26+
const convertToBasePathRedirects = ({ basePath, nextRedirects }) => {
27+
if (basePath === '') return nextRedirects
28+
const basePathRedirects = getBasePathDefaultRedirects({ basePath, nextRedirects })
29+
nextRedirects.forEach((r) => {
30+
if (r.route === '/') {
31+
const indexRedirects = [
32+
{
33+
route: '/',
34+
target: basePath,
35+
statusCode: '301',
36+
force: true,
37+
},
38+
{
39+
route: basePath,
40+
target: r.target,
41+
},
42+
]
43+
basePathRedirects.push(...indexRedirects)
44+
} else if (!r.route.includes('_next/') && r.target.includes('/.netlify/functions') && r.conditions) {
45+
// If preview mode redirect
46+
basePathRedirects.push({
47+
route: `${basePath}${r.route}`,
48+
target: r.target,
49+
conditions: r.conditions,
50+
})
51+
} else if (!r.route.includes('_next/') && r.target.includes('/.netlify/functions')) {
52+
const functionRedirects = [
53+
{
54+
route: r.route,
55+
target: `${basePath}${r.route}`,
56+
statusCode: '301',
57+
force: true,
58+
},
59+
{
60+
route: `${basePath}${r.route}`,
61+
target: r.target,
62+
},
63+
]
64+
basePathRedirects.push(...functionRedirects)
65+
} else {
66+
basePathRedirects.push(r)
67+
}
68+
})
69+
return basePathRedirects
70+
}
71+
72+
module.exports = convertToBasePathRedirects

‎src/lib/steps/setupRedirects.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ const { join } = require('path')
22

33
const { existsSync, readFileSync, writeFileSync } = require('fs-extra')
44

5+
const getNextConfig = require('../../../helpers/getNextConfig')
56
const { CUSTOM_REDIRECTS_PATH, NEXT_IMAGE_FUNCTION_NAME } = require('../config')
7+
const { DYNAMIC_PARAMETER_REGEX } = require('../constants/regex')
8+
const convertToBasePathRedirects = require('../helpers/convertToBasePathRedirects')
69
const getNetlifyRoutes = require('../helpers/getNetlifyRoutes')
710
const getSortedRedirects = require('../helpers/getSortedRedirects')
811
const isDynamicRoute = require('../helpers/isDynamicRoute')
@@ -31,7 +34,7 @@ const setupRedirects = async (publishPath) => {
3134
const getSPRevalidateRedirects = require('../pages/getStaticPropsWithRevalidate/redirects')
3235
const getWithoutPropsRedirects = require('../pages/withoutProps/redirects')
3336

34-
const nextRedirects = [
37+
let nextRedirects = [
3538
...(await getApiRedirects()),
3639
...(await getInitialPropsRedirects()),
3740
...(await getServerSidePropsRedirects()),
@@ -44,12 +47,18 @@ const setupRedirects = async (publishPath) => {
4447
// Add _redirect section heading
4548
redirects.push('# Next-on-Netlify Redirects')
4649

50+
const { basePath } = await getNextConfig()
51+
const hasBasePath = basePath !== ''
52+
if (hasBasePath) {
53+
nextRedirects = convertToBasePathRedirects({ basePath, nextRedirects })
54+
}
55+
4756
const staticRedirects = nextRedirects.filter(({ route }) => !isDynamicRoute(removeFileExtension(route)))
4857
const dynamicRedirects = nextRedirects.filter(({ route }) => isDynamicRoute(removeFileExtension(route)))
4958

5059
// Add necessary next/image redirects for our image function
5160
dynamicRedirects.push({
52-
route: '/_next/image* url=:url w=:width q=:quality',
61+
route: `${basePath || ''}/_next/image* url=:url w=:width q=:quality`,
5362
target: `/nextimg/:url/:width/:quality`,
5463
statusCode: '301',
5564
force: true,
@@ -62,13 +71,22 @@ const setupRedirects = async (publishPath) => {
6271
const sortedStaticRedirects = getSortedRedirects(staticRedirects)
6372
const sortedDynamicRedirects = getSortedRedirects(dynamicRedirects)
6473

74+
const basePathSortFunc = (a, b) => (a.target.includes(basePath) ? -1 : 1)
75+
const allRedirects = hasBasePath
76+
? [...sortedStaticRedirects.sort(basePathSortFunc), ...sortedDynamicRedirects.sort(basePathSortFunc)]
77+
: [...sortedStaticRedirects, ...sortedDynamicRedirects]
78+
6579
// Assemble redirects for each route
66-
;[...sortedStaticRedirects, ...sortedDynamicRedirects].forEach((nextRedirect) => {
80+
allRedirects.forEach((nextRedirect) => {
6781
// One route may map to multiple Netlify routes: e.g., catch-all pages
6882
// require two Netlify routes in the _redirects file
6983
getNetlifyRoutes(nextRedirect.route).forEach((netlifyRoute) => {
7084
const { conditions = [], force = false, statusCode = '200', target } = nextRedirect
71-
const redirectPieces = [netlifyRoute, target, `${statusCode}${force ? '!' : ''}`, conditions.join(' ')]
85+
const formattedTarget =
86+
hasBasePath && target.includes(basePath)
87+
? target.replace(DYNAMIC_PARAMETER_REGEX, '/:$1').replace('...', '')
88+
: target
89+
const redirectPieces = [netlifyRoute, formattedTarget, `${statusCode}${force ? '!' : ''}`, conditions.join(' ')]
7290
const redirect = redirectPieces.join(' ').trim()
7391
logItem(redirect)
7492
redirects.push(redirect)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Routing creates Netlify redirects 1`] = `
4+
"# Next-on-Netlify Redirects
5+
/getStaticProps/withRevalidate/2 /foo/getStaticProps/withRevalidate/2 301!
6+
/getStaticProps/withRevalidate/1 /foo/getStaticProps/withRevalidate/1 301!
7+
/getStaticProps/with-revalidate /foo/getStaticProps/with-revalidate 301!
8+
/getServerSideProps/static /foo/getServerSideProps/static 301!
9+
/api/static /foo/api/static 301!
10+
/api/hello-background /foo/api/hello-background 301!
11+
/ /foo 301!
12+
/_next/data/%BUILD_ID%/getServerSideProps/static.json /.netlify/functions/next_getServerSideProps_static 200
13+
/_next/data/%BUILD_ID%/getStaticProps/1.json /.netlify/functions/next_getStaticProps_id 200! Cookie=__prerender_bypass,__next_preview_data
14+
/_next/data/%BUILD_ID%/getStaticProps/2.json /.netlify/functions/next_getStaticProps_id 200! Cookie=__prerender_bypass,__next_preview_data
15+
/_next/data/%BUILD_ID%/getStaticProps/static.json /.netlify/functions/next_getStaticProps_static 200! Cookie=__prerender_bypass,__next_preview_data
16+
/_next/data/%BUILD_ID%/getStaticProps/with-revalidate.json /.netlify/functions/next_getStaticProps_withrevalidate 200
17+
/_next/data/%BUILD_ID%/getStaticProps/withFallback/3.json /.netlify/functions/next_getStaticProps_withFallback_id 200! Cookie=__prerender_bypass,__next_preview_data
18+
/_next/data/%BUILD_ID%/getStaticProps/withFallback/4.json /.netlify/functions/next_getStaticProps_withFallback_id 200! Cookie=__prerender_bypass,__next_preview_data
19+
/_next/data/%BUILD_ID%/getStaticProps/withFallback/my/path/1.json /.netlify/functions/next_getStaticProps_withFallback_slug 200! Cookie=__prerender_bypass,__next_preview_data
20+
/_next/data/%BUILD_ID%/getStaticProps/withFallback/my/path/2.json /.netlify/functions/next_getStaticProps_withFallback_slug 200! Cookie=__prerender_bypass,__next_preview_data
21+
/_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/3.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200! Cookie=__prerender_bypass,__next_preview_data
22+
/_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/4.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200! Cookie=__prerender_bypass,__next_preview_data
23+
/_next/data/%BUILD_ID%/getStaticProps/withRevalidate/1.json /.netlify/functions/next_getStaticProps_withRevalidate_id 200
24+
/_next/data/%BUILD_ID%/getStaticProps/withRevalidate/2.json /.netlify/functions/next_getStaticProps_withRevalidate_id 200
25+
/foo /.netlify/functions/next_index 200
26+
/foo/_next/* /_next/:splat 301!
27+
/foo/api/hello-background /.netlify/functions/next_api_hello-background 200
28+
/foo/api/static /.netlify/functions/next_api_static 200
29+
/foo/getServerSideProps/static /.netlify/functions/next_getServerSideProps_static 200
30+
/foo/getStaticProps/1 /.netlify/functions/next_getStaticProps_id 200 Cookie=__prerender_bypass,__next_preview_data
31+
/foo/getStaticProps/2 /.netlify/functions/next_getStaticProps_id 200 Cookie=__prerender_bypass,__next_preview_data
32+
/foo/getStaticProps/static /.netlify/functions/next_getStaticProps_static 200 Cookie=__prerender_bypass,__next_preview_data
33+
/foo/getStaticProps/with-revalidate /.netlify/functions/next_getStaticProps_withrevalidate 200
34+
/foo/getStaticProps/withFallback/3 /.netlify/functions/next_getStaticProps_withFallback_id 200 Cookie=__prerender_bypass,__next_preview_data
35+
/foo/getStaticProps/withFallback/4 /.netlify/functions/next_getStaticProps_withFallback_id 200 Cookie=__prerender_bypass,__next_preview_data
36+
/foo/getStaticProps/withFallback/my/path/1 /.netlify/functions/next_getStaticProps_withFallback_slug 200 Cookie=__prerender_bypass,__next_preview_data
37+
/foo/getStaticProps/withFallback/my/path/2 /.netlify/functions/next_getStaticProps_withFallback_slug 200 Cookie=__prerender_bypass,__next_preview_data
38+
/foo/getStaticProps/withFallbackBlocking/3 /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 Cookie=__prerender_bypass,__next_preview_data
39+
/foo/getStaticProps/withFallbackBlocking/4 /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 Cookie=__prerender_bypass,__next_preview_data
40+
/foo/getStaticProps/withRevalidate/1 /.netlify/functions/next_getStaticProps_withRevalidate_id 200
41+
/foo/getStaticProps/withRevalidate/2 /.netlify/functions/next_getStaticProps_withRevalidate_id 200
42+
/shows/:params/* /foo/shows/:params 301!
43+
/shows/:id /foo/shows/:id 301!
44+
/getStaticProps/withRevalidate/withFallback/:id /foo/getStaticProps/withRevalidate/withFallback/:id 301!
45+
/getStaticProps/withFallbackBlocking/:id /foo/getStaticProps/withFallbackBlocking/:id 301!
46+
/getStaticProps/withFallback/:slug/* /foo/getStaticProps/withFallback/:slug 301!
47+
/getStaticProps/withFallback/:id /foo/getStaticProps/withFallback/:id 301!
48+
/getServerSideProps/:id /foo/getServerSideProps/:id 301!
49+
/getServerSideProps/all /foo/getServerSideProps/all/:[slug] 301!
50+
/getServerSideProps/all/* /foo/getServerSideProps/all/:[slug] 301!
51+
/api/shows/:params/* /foo/api/shows/:params 301!
52+
/api/shows/:id /foo/api/shows/:id 301!
53+
/_next/data/%BUILD_ID%/getServerSideProps/all.json /.netlify/functions/next_getServerSideProps_all_slug 200
54+
/_next/data/%BUILD_ID%/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200
55+
/_next/data/%BUILD_ID%/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200
56+
/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200
57+
/_next/data/%BUILD_ID%/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200
58+
/_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200
59+
/_next/data/%BUILD_ID%/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200
60+
/foo/_next/image* url=:url w=:width q=:quality /nextimg/:url/:width/:quality 301!
61+
/foo/api/shows/:id /.netlify/functions/next_api_shows_id 200
62+
/foo/api/shows/:params/* /.netlify/functions/next_api_shows_params 200
63+
/foo/getServerSideProps/all /.netlify/functions/next_getServerSideProps_all_slug 200
64+
/foo/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200
65+
/foo/getServerSideProps/:id /.netlify/functions/next_getServerSideProps_id 200
66+
/foo/getStaticProps/withFallback/:id /.netlify/functions/next_getStaticProps_withFallback_id 200
67+
/foo/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200
68+
/foo/getStaticProps/withFallbackBlocking/:id /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200
69+
/foo/getStaticProps/withRevalidate/withFallback/:id /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200
70+
/foo/shows/:id /.netlify/functions/next_shows_id 200
71+
/foo/shows/:params/* /.netlify/functions/next_shows_params 200
72+
/nextimg/* /.netlify/functions/next_image 200
73+
/static/:id /static/[id].html 200"
74+
`;

‎src/tests/basePath.test.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Test next-on-netlify when a custom distDir is set in next.config.js
2+
3+
const { EOL } = require('os')
4+
const { parse, join } = require('path')
5+
const { readFileSync } = require('fs-extra')
6+
const buildNextApp = require('./helpers/buildNextApp')
7+
8+
// The name of this test file (without extension)
9+
const FILENAME = parse(__filename).name
10+
11+
// The directory which will be used for testing.
12+
// We simulate a NextJS app within that directory, with pages, and a
13+
// package.json file.
14+
const PROJECT_PATH = join(__dirname, 'builds', FILENAME)
15+
16+
// Capture the output to verify successful build
17+
let buildOutput
18+
19+
beforeAll(
20+
async () => {
21+
buildOutput = await buildNextApp()
22+
.forTest(__filename)
23+
.withPages('pages')
24+
.withNextConfig('next.config.js-with-basePath.js')
25+
.withPackageJson('package.json')
26+
.build()
27+
},
28+
// time out after 180 seconds
29+
180 * 1000,
30+
)
31+
32+
describe('next-on-netlify', () => {
33+
test('builds successfully', () => {
34+
expect(buildOutput).toMatch('Next on Netlify')
35+
expect(buildOutput).toMatch('Success! All done!')
36+
})
37+
})
38+
39+
describe('Routing', () => {
40+
test('creates Netlify redirects', async () => {
41+
// Read _redirects file
42+
const contents = readFileSync(join(PROJECT_PATH, 'out_publish', '_redirects'))
43+
let redirects = contents.toString()
44+
45+
// Replace non-persistent build ID with placeholder
46+
redirects = redirects.replace(/\/_next\/data\/[^\/]+\//g, '/_next/data/%BUILD_ID%/')
47+
48+
// Check that redirects match
49+
expect(redirects).toMatchSnapshot()
50+
})
51+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
target: 'serverless',
3+
basePath: '/foo',
4+
}

0 commit comments

Comments
 (0)
Please sign in to comment.