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

Commit 7c353b0

Browse files
committed
Add support for custom NextJS build directory
By default, next-on-netlify looks in the .next directory for the built NextJS app. However, if a custom distDir is specified in next.config.js, next-on-netlify looks for that directory instead.
1 parent 93585b4 commit 7c353b0

File tree

6 files changed

+195
-13
lines changed

6 files changed

+195
-13
lines changed

lib/collectNextjsPages.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// - Ignore API routes/pages
77
// - Public files are not tracked in manifest
88
// - Structure is an array of objects (was an object of objects)
9+
// - Function now handles custom NextJS distDir
910

1011
const readPagesManifest = require('./readPagesManifest')
1112
const getNetlifyRoute = require('./getNetlifyRoute')
@@ -15,8 +16,8 @@ const pathToRegexStr = require("./serverless-next.js/pathToRegexStr")
1516

1617
const isHtmlPage = p => p.endsWith(".html");
1718

18-
function collectNextjsPages() {
19-
const pagesManifest = readPagesManifest();
19+
function collectNextjsPages({ nextDistDir }) {
20+
const pagesManifest = readPagesManifest({ nextDistDir });
2021

2122
const pages = Object.entries(pagesManifest)
2223

lib/getNextDistDir.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Get the NextJS distDir specified in next.config.js
2+
const { pathExistsSync } = require('fs-extra')
3+
const { resolve, join } = require('path')
4+
5+
// The default dist dir used by NextJS, if not specified otherwise by user
6+
const DEFAULT_DIST_DIR = join(".", ".next")
7+
8+
const getNextDistDir = ({ nextConfigPath }) => {
9+
// If next.config.js does not exists, default to NextJS' default distDir
10+
hasNextConfig = pathExistsSync(nextConfigPath)
11+
if(!hasNextConfig)
12+
return DEFAULT_DIST_DIR
13+
14+
// Read next.config.js
15+
const resolvedNextConfigPath = resolve(".", nextConfigPath)
16+
const nextConfig = require(resolvedNextConfigPath)
17+
18+
// If distDir is not set, default to NextJS' default distDir
19+
const hasDistDir = 'distDir' in nextConfig
20+
if(!hasDistDir)
21+
return DEFAULT_DIST_DIR
22+
23+
// Return distDir specified by user
24+
return join(".", nextConfig.distDir)
25+
}
26+
27+
28+
module.exports = getNextDistDir

lib/readPagesManifest.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22
// See original file in serverless-next.js folder.
33
// Changes:
44
// - The function is now synchronous (it used to by async)
5+
// - The function now handles custom NextJS distDir
56

67
const { join } = require("path")
78
const fse = require("fs-extra")
89
const isDynamicRoute = require("./serverless-next.js/isDynamicRoute")
910
const getSortedRoutes = require("./serverless-next.js/sortedRoutes")
1011

1112

12-
function readPagesManifest() {
13-
const path = join(".next/serverless/pages-manifest.json");
13+
function readPagesManifest({ nextDistDir }) {
14+
const path = join(nextDistDir, "/serverless/pages-manifest.json");
1415
const hasServerlessPageManifest = fse.existsSync(path);
1516

1617
if (!hasServerlessPageManifest) {

next-on-netlify.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ const { copySync, emptyDirSync,
55
writeFileSync, writeJsonSync } = require('fs-extra')
66
const { join } = require('path')
77
const collectNextjsPages = require('./lib/collectNextjsPages')
8+
const getNextDistDir = require('./lib/getNextDistDir')
89

910
// Configuration: Input Paths
11+
// Path to the NextJS config file
12+
const NEXT_CONFIG_PATH = join(".", "next.config.js")
1013
// Path to the folder that NextJS builds to
11-
const NEXT_BUILD_PATH = join(".", ".next")
14+
const NEXT_DIST_DIR = getNextDistDir({ nextConfigPath: NEXT_CONFIG_PATH })
1215
// Path to Netlify functions folder
1316
const FUNCTIONS_PATH = join(".", "functions")
1417
// Path to public folder
@@ -30,13 +33,17 @@ const ROUTER_FUNCTION_NAME = "nextRouter"
3033
// - regex (/posts/([^\/]+)/)
3134
// - isHTML (whether it is a static file)
3235
// - isDynamic (whether it contains dynamic URL segments)
36+
const pages = collectNextjsPages({ nextDistDir: NEXT_DIST_DIR })
37+
3338
// We ignore app.js and document.js, since NextJS has already included them in
3439
// all of the pages.
35-
const pages = collectNextjsPages().filter(({ file }) => (
40+
const filteredPages = pages.filter(({ file }) => (
3641
file !== "pages/_app.js" && file !== "pages/_document.js"
3742
))
38-
const ssrPages = pages.filter(({ isHTML }) => !isHTML)
39-
const htmlPages = pages.filter(({ isHTML }) => isHTML)
43+
44+
// Identify SSR and HTML pages
45+
const ssrPages = filteredPages.filter(({ isHTML }) => !isHTML)
46+
const htmlPages = filteredPages.filter(({ isHTML }) => isHTML)
4047

4148

4249
// 2. SSR Setup
@@ -56,7 +63,7 @@ copySync(
5663
// From there, they can be served by our nextRouter Netlify function.
5764
ssrPages.forEach(({ file }) => {
5865
copySync(
59-
join(NEXT_BUILD_PATH, "serverless", file),
66+
join(NEXT_DIST_DIR, "serverless", file),
6067
join(FUNCTIONS_PATH, ROUTER_FUNCTION_NAME, file)
6168
)
6269
})
@@ -93,8 +100,8 @@ emptyDirSync(
93100
// These are static, so they do not need to be handled by our nextRouter.
94101
htmlPages.forEach(({ file }) => (
95102
copySync(
96-
join(NEXT_BUILD_PATH, "serverless", file),
97-
join(PUBLIC_PATH, "_next", file)
103+
join(NEXT_DIST_DIR, "serverless", file),
104+
join(PUBLIC_PATH, "_next", file)
98105
)
99106
))
100107

@@ -103,8 +110,8 @@ htmlPages.forEach(({ file }) => (
103110
// Copy the NextJS' static assets from /.next/static to /public/_next/static.
104111
// These need to be available for NextJS to work.
105112
copySync(
106-
join(NEXT_BUILD_PATH, "static"),
107-
join(PUBLIC_PATH, "_next", "static")
113+
join(NEXT_DIST_DIR, "static"),
114+
join(PUBLIC_PATH, "_next", "static")
108115
)
109116

110117

tests/customNextDistDir.test.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Test next-on-netlify when a custom distDir is set in next.config.js
2+
const { parse, join } = require('path')
3+
const { copySync, emptyDirSync, existsSync,
4+
readdirSync, readFileSync, readJsonSync } = require('fs-extra')
5+
const npmRunBuild = require("./helpers/npmRunBuild")
6+
7+
// The name of this test file (without extension)
8+
const FILENAME = parse(__filename).name
9+
10+
// The directory which will be used for testing.
11+
// We simulate a NextJS app within that directory, with pages, and a
12+
// package.json file.
13+
const PROJECT_PATH = join(__dirname, "builds", FILENAME)
14+
15+
// The directory that contains the fixtures, such as NextJS pages,
16+
// NextJS config, and package.json
17+
const FIXTURE_PATH = join(__dirname, "fixtures")
18+
19+
// Capture the output of `npm run build` to verify successful build
20+
let BUILD_OUTPUT
21+
22+
beforeAll(
23+
async () => {
24+
// Clear project directory
25+
emptyDirSync(PROJECT_PATH)
26+
emptyDirSync(join(PROJECT_PATH, "pages"))
27+
28+
// Copy NextJS pages and config
29+
copySync(
30+
join(FIXTURE_PATH, "pages"),
31+
join(PROJECT_PATH, "pages")
32+
)
33+
copySync(
34+
join(FIXTURE_PATH, "next.config.js-with-distDir.js"),
35+
join(PROJECT_PATH, "next.config.js")
36+
)
37+
38+
// Copy package.json
39+
copySync(
40+
join(FIXTURE_PATH, "package.json"),
41+
join(PROJECT_PATH, "package.json")
42+
)
43+
44+
// Invoke `npm run build`: Build Next and run next-on-netlify
45+
const { stdout } = await npmRunBuild({ directory: PROJECT_PATH })
46+
BUILD_OUTPUT = stdout
47+
},
48+
// time out after 30 seconds
49+
30 * 1000
50+
)
51+
52+
describe('Next', () => {
53+
test('builds successfully', () => {
54+
expect(BUILD_OUTPUT).toMatch("Creating an optimized production build...")
55+
expect(BUILD_OUTPUT).toMatch("Automatically optimizing pages...")
56+
expect(BUILD_OUTPUT).toMatch("First Load JS shared by all")
57+
})
58+
})
59+
60+
describe('SSR Pages', () => {
61+
const router = join(PROJECT_PATH, "functions", "nextRouter")
62+
63+
test('creates nextRouter.js Netlify Function', () => {
64+
expect(existsSync(join(router, "nextRouter.js"))).toBe(true)
65+
})
66+
67+
test('lists all routes in routes.json', () => {
68+
// read routes
69+
const { routes } = readJsonSync(join(router, "routes.json"))
70+
71+
// check entries
72+
expect(routes).toContainEqual({
73+
file: "pages/index.js",
74+
regex: "^\\/(?:\\/)?$"
75+
})
76+
expect(routes).toContainEqual({
77+
file: "pages/shows/[id].js",
78+
regex: "^\\/shows\\/([^\\/]+?)(?:\\/)?$"
79+
})
80+
expect(routes).toContainEqual({
81+
file: "pages/shows/[...params].js",
82+
regex: "^\\/shows(?:\\/((?:[^\\/]+?)(?:\\/(?:[^\\/]+?))*))?(?:\\/)?$"
83+
})
84+
})
85+
86+
test('requires all pages in allPages.js', () => {
87+
// read allPages.js
88+
const contents = readFileSync(join(router, "allPages.js"))
89+
90+
// Convert contents into an array, each line being one element
91+
const requires = contents.toString().split("\n")
92+
93+
// Verify presence of require statements
94+
expect(requires).toContain('require("./pages/index.js")')
95+
expect(requires).toContain('require("./pages/shows/[id].js")')
96+
expect(requires).toContain('require("./pages/shows/[...params].js")')
97+
})
98+
99+
test('bundles all SSR-pages in /pages', () => {
100+
const pages = join(PROJECT_PATH, "public", "_next", "pages")
101+
102+
expect(existsSync(join(router, "pages", "index.js"))).toBe(true)
103+
expect(existsSync(join(router, "pages", "shows", "[id].js"))).toBe(true)
104+
expect(existsSync(join(router, "pages", "shows", "[...params].js"))).toBe(true)
105+
})
106+
})
107+
108+
describe('Static Pages', () => {
109+
test('copies static pages to public/_next/ directory', () => {
110+
const pages = join(PROJECT_PATH, "public", "_next", "pages")
111+
112+
expect(existsSync(join(pages, "static.html"))).toBe(true)
113+
expect(existsSync(join(pages, "static/[id].html"))).toBe(true)
114+
})
115+
116+
test('copies static assets to public/_next/ directory', () => {
117+
const dirs = readdirSync(join(PROJECT_PATH, "public", "_next", "static"))
118+
119+
expect(dirs.length).toBe(3)
120+
expect(dirs).toContain("chunks")
121+
expect(dirs).toContain("runtime")
122+
})
123+
})
124+
125+
describe('Routing',() => {
126+
test('creates Netlify redirects', async () => {
127+
// Read _redirects file
128+
const contents = readFileSync(join(PROJECT_PATH, "public", "_redirects"))
129+
130+
// Convert contents into an array, each line being one element
131+
const redirects = contents.toString().split("\n")
132+
133+
// Check that routes are present
134+
expect(redirects).toContain("/static /_next/pages/static.html 200")
135+
expect(redirects).toContain("/static/:id /_next/pages/static/[id].html 200")
136+
expect(redirects).toContain("/ /.netlify/functions/nextRouter?_path=/ 200")
137+
expect(redirects).toContain("/index /.netlify/functions/nextRouter?_path=/index 200")
138+
expect(redirects).toContain("/shows/:id /.netlify/functions/nextRouter?_path=/shows/:id 200")
139+
expect(redirects).toContain("/shows/* /.netlify/functions/nextRouter?_path=/shows/* 200")
140+
})
141+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
target: 'serverless',
3+
distDir: '.myCustomDir'
4+
};

0 commit comments

Comments
 (0)