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

Commit f406eff

Browse files
committed
Create next-on-netlify bin command
Run the `next-on-netlify` command to set up any NextJS application for hosting on Netlify. It sets up a nextRouter Netlify function with SSR pages. And it copies HTML pages and NextJS assets to the public directory. Finally, it sets up the necessary Netlify _redirects to route requests to the nextRouter Netlify function or the HTML pages. You must build NextJS before running `next-on-netlify`.
1 parent ffb49f8 commit f406eff

14 files changed

+597
-6
lines changed

index.js

-3
This file was deleted.

lib/collectNextjsPages.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Adapted from serverless-next.js (v1.8.0)
2+
// See original file in serverless-next.js folder.
3+
// Changes:
4+
// - Function is now collectNextjsPages (was prepareBuildManifests)
5+
// - Function is now synchronous (used to be async)
6+
// - Ignore API routes/pages
7+
// - Public files are not tracked in manifest
8+
// - Structure is an array of objects (was an object of objects)
9+
10+
const readPagesManifest = require('./readPagesManifest')
11+
const isDynamicRoute = require("./serverless-next.js/isDynamicRoute")
12+
const expressifyDynamicRoute = require("./serverless-next.js/expressifyDynamicRoute")
13+
const pathToRegexStr = require("./serverless-next.js/pathToRegexStr")
14+
15+
const isHtmlPage = p => p.endsWith(".html");
16+
17+
function collectNextjsPages() {
18+
const pagesManifest = readPagesManifest();
19+
20+
const pages = Object.entries(pagesManifest)
21+
22+
return pages.map(([route, pageFile]) => {
23+
24+
// For each page, add an entry to our manifest
25+
const page = {
26+
// path to the page (/pages/about)
27+
file: pageFile,
28+
// whether the page includes dynamic URL parameters (/posts/[id])
29+
isDynamic: isDynamicRoute(route),
30+
// whether the page is HTML (does not use getInitialProps)
31+
isHTML: isHtmlPage(pageFile)
32+
}
33+
34+
// Route to the page (/about)
35+
// If the page is dynamic, use url segments: /posts/[id] --> /posts/:id
36+
page.route = page.isDynamic ? expressifyDynamicRoute(route) : route
37+
38+
// Regex for matching the page (/^\/about$/)
39+
page.regex = pathToRegexStr(page.route)
40+
41+
return page
42+
})
43+
}
44+
45+
module.exports = collectNextjsPages

lib/readPagesManifest.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Adapted from serverless-next.js (v1.8.0)
2+
// See original file in serverless-next.js folder.
3+
// Changes:
4+
// - The function is now synchronous (it used to by async)
5+
6+
const { join } = require("path")
7+
const fse = require("fs-extra")
8+
const isDynamicRoute = require("./serverless-next.js/isDynamicRoute")
9+
const getSortedRoutes = require("./serverless-next.js/sortedRoutes")
10+
11+
12+
function readPagesManifest() {
13+
const path = join(".next/serverless/pages-manifest.json");
14+
const hasServerlessPageManifest = fse.existsSync(path);
15+
16+
if (!hasServerlessPageManifest) {
17+
throw "pages-manifest not found. Check if `next.config.js` target is set to 'serverless'"
18+
}
19+
20+
const pagesManifest = fse.readJSONSync(path);
21+
const pagesManifestWithoutDynamicRoutes = Object.keys(pagesManifest).reduce(
22+
(acc, route) => {
23+
if (isDynamicRoute(route)) {
24+
return acc;
25+
}
26+
27+
acc[route] = pagesManifest[route];
28+
return acc;
29+
},
30+
{}
31+
);
32+
33+
const dynamicRoutedPages = Object.keys(pagesManifest).filter(
34+
isDynamicRoute
35+
);
36+
const sortedDynamicRoutedPages = getSortedRoutes(dynamicRoutedPages);
37+
38+
const sortedPagesManifest = pagesManifestWithoutDynamicRoutes;
39+
40+
sortedDynamicRoutedPages.forEach(route => {
41+
sortedPagesManifest[route] = pagesManifest[route];
42+
});
43+
44+
return sortedPagesManifest;
45+
}
46+
47+
module.exports = readPagesManifest

lib/routerTemplate.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// TEMPLATE: This file will be copied to the Netlify functions directory when
2+
// running next-on-netlify/run.js
3+
4+
const compat = require("next-aws-lambda")
5+
const { routes } = require("./routes.json")
6+
7+
// We have to require all pages here so that Netlify's zip-it-and-ship-it
8+
// method will bundle them. That makes them available for our dynamic require
9+
// statement later.
10+
// We wrap this require in if(false) to make sure it is *not* executed when the
11+
// function runs.
12+
if (false) {
13+
require("./allPages")
14+
}
15+
16+
// Look through all routes and check each regex against the request URL
17+
const getRoute = path => {
18+
route = routes.find(({ regex }) => {
19+
const re = new RegExp(regex, "i");
20+
return re.test(path);
21+
})
22+
23+
// Return the route or the error page
24+
return route || { file: "pages/_error.js" }
25+
}
26+
27+
exports.handler = (event, context, callback) => {
28+
// The following lines set event.path in development environments.
29+
// There is a difference in how Netlify handles redirects locally vs
30+
// production. Locally, event.path is set to the target of the redirect:
31+
// /.netlify/functions/nextRouter?_path=...
32+
// Deployed on Netlify, event.path is the source of the redirect: /posts/3
33+
const isProduction = context.hasOwnProperty('awsRequestId')
34+
if(!isProduction) {
35+
event.path = event.queryStringParameters._path
36+
}
37+
38+
// Get the request URL
39+
const { path } = event
40+
console.log("[request]", path)
41+
42+
43+
// Identify the file to render
44+
const { file } = getRoute(path)
45+
console.log("[render] ", file)
46+
47+
48+
// Load the page to render
49+
// Do not do this: const page = require(`./${file}`)
50+
// Otherwise, Netlify's zip-it-and-ship-it will attempt to bundle "./"
51+
// into the function's zip folder and the build will fail
52+
const pathToFile = `./${file}`
53+
const page = require(pathToFile)
54+
55+
56+
// Render the page
57+
compat(page)(
58+
{
59+
...event,
60+
// Required. Otherwise, compat() will complain
61+
requestContext: {},
62+
// Optional: Pass additional parameters to NextJS
63+
multiValueQueryStringParameters: {}
64+
},
65+
context,
66+
callback
67+
)
68+
};

lib/serverless-next.js/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# About the files in this directory
2+
3+
The files in this directory are exact copies of files found in [the `serverless-next.js` repository](https://github.com/danielcondemarin/serverless-next.js/tree/master/packages/serverless-nextjs-component).
4+
5+
The `serverless-next.js` repository is licensed as MIT.
6+
7+
Big shoutout to [@danielcondemarin](https://github.com/danielcondemarin) and all the other contributors of the package. 🙌
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copied from serverless-next.js (v1.8.0)
2+
// https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/lib/expressifyDynamicRoute.js
3+
4+
// converts a nextjs dynamic route /[param]/ to express style /:param/
5+
module.exports = dynamicRoute => {
6+
return dynamicRoute.replace(/\[(?<param>.*?)]/g, ":$<param>");
7+
};
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copied from serverless-next.js (v1.8.0)
2+
// https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/lib/isDynamicRoute.js
3+
4+
module.exports = route => {
5+
// Identify /[param]/ in route string
6+
return /\/\[[^\/]+?\](?=\/|$)/.test(route);
7+
};
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copied from serverless-next.js (v1.8.0)
2+
// https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/lib/pathToRegexStr.js
3+
4+
const pathToRegexp = require("path-to-regexp");
5+
6+
module.exports = path =>
7+
pathToRegexp(path)
8+
.toString()
9+
.replace(/\/(.*)\/\i/, "$1");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copied from serverless-next.js (v1.8.0)
2+
// https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/serverless.js
3+
// This file is for reference purposes only.
4+
5+
async prepareBuildManifests(nextConfigPath) {
6+
const pagesManifest = await this.readPagesManifest(nextConfigPath);
7+
8+
const defaultBuildManifest = {
9+
pages: {
10+
ssr: {
11+
dynamic: {},
12+
nonDynamic: {}
13+
},
14+
html: {
15+
dynamic: {},
16+
nonDynamic: {}
17+
}
18+
},
19+
publicFiles: {},
20+
cloudFrontOrigins: {}
21+
};
22+
23+
const apiBuildManifest = {
24+
apis: {
25+
dynamic: {},
26+
nonDynamic: {}
27+
}
28+
};
29+
30+
const ssrPages = defaultBuildManifest.pages.ssr;
31+
const htmlPages = defaultBuildManifest.pages.html;
32+
const apiPages = apiBuildManifest.apis;
33+
34+
const isHtmlPage = p => p.endsWith(".html");
35+
const isApiPage = p => p.startsWith("pages/api");
36+
37+
Object.entries(pagesManifest).forEach(([route, pageFile]) => {
38+
const dynamicRoute = isDynamicRoute(route);
39+
const expressRoute = dynamicRoute ? expressifyDynamicRoute(route) : null;
40+
41+
if (isHtmlPage(pageFile)) {
42+
if (dynamicRoute) {
43+
htmlPages.dynamic[expressRoute] = {
44+
file: pageFile,
45+
regex: pathToRegexStr(expressRoute)
46+
};
47+
} else {
48+
htmlPages.nonDynamic[route] = pageFile;
49+
}
50+
} else if (isApiPage(pageFile)) {
51+
if (dynamicRoute) {
52+
apiPages.dynamic[expressRoute] = {
53+
file: pageFile,
54+
regex: pathToRegexStr(expressRoute)
55+
};
56+
} else {
57+
apiPages.nonDynamic[route] = pageFile;
58+
}
59+
} else if (dynamicRoute) {
60+
ssrPages.dynamic[expressRoute] = {
61+
file: pageFile,
62+
regex: pathToRegexStr(expressRoute)
63+
};
64+
} else {
65+
ssrPages.nonDynamic[route] = pageFile;
66+
}
67+
});
68+
69+
const publicFiles = await this.readPublicFiles(nextConfigPath);
70+
71+
publicFiles.forEach(pf => {
72+
defaultBuildManifest.publicFiles["/" + pf] = pf;
73+
});
74+
75+
return {
76+
defaultBuildManifest,
77+
apiBuildManifest
78+
};
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Original function from serverless-next.js (v1.8.0)
2+
// https://github.com/danielcondemarin/serverless-next.js/blob/master/packages/serverless-nextjs-component/serverless.js
3+
// This file is for reference purposes only.
4+
5+
async readPagesManifest(nextConfigPath) {
6+
const path = join(nextConfigPath, ".next/serverless/pages-manifest.json");
7+
const hasServerlessPageManifest = await fse.exists(path);
8+
9+
if (!hasServerlessPageManifest) {
10+
return Promise.reject(
11+
"pages-manifest not found. Check if `next.config.js` target is set to 'serverless'"
12+
);
13+
}
14+
15+
const pagesManifest = await fse.readJSON(path);
16+
const pagesManifestWithoutDynamicRoutes = Object.keys(pagesManifest).reduce(
17+
(acc, route) => {
18+
if (isDynamicRoute(route)) {
19+
return acc;
20+
}
21+
22+
acc[route] = pagesManifest[route];
23+
return acc;
24+
},
25+
{}
26+
);
27+
28+
const dynamicRoutedPages = Object.keys(pagesManifest).filter(
29+
isDynamicRoute
30+
);
31+
const sortedDynamicRoutedPages = getSortedRoutes(dynamicRoutedPages);
32+
33+
const sortedPagesManifest = pagesManifestWithoutDynamicRoutes;
34+
35+
sortedDynamicRoutedPages.forEach(route => {
36+
sortedPagesManifest[route] = pagesManifest[route];
37+
});
38+
39+
return sortedPagesManifest;
40+
}

0 commit comments

Comments
 (0)