18 | );
19 |
20 | export async function getStaticProps(context) {
21 | const res = await fetch(`https://api.tvmaze.com/shows/71`);
22 | const data = await res.json();
23 |
24 | return {
25 | props: {
26 | show: data,
27 | },
28 | revalidate: 1,
29 | };
30 | }
31 |
32 | export default Show;
33 |
--------------------------------------------------------------------------------
/cypress/fixtures/pages/api/shows/[...params].js:
--------------------------------------------------------------------------------
1 | export default async (req, res) => {
2 | // Get the params and query string parameters
3 | const { query } = req;
4 | const { params, ...queryStringParams } = query;
5 |
6 | // Get the ID of the show
7 | const id = params[0];
8 |
9 | // Get the data
10 | const fetchRes = await fetch(`https://api.tvmaze.com/shows/${id}`);
11 | const data = await fetchRes.json();
12 |
13 | // If show was found, return it
14 | if (fetchRes.status == 200) {
15 | res.status(200);
16 | res.json({ params, queryStringParams, show: data });
17 | }
18 | // If show was not found, return error
19 | else {
20 | res.status(404);
21 | res.json({ error: "Show not found" });
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/cypress/fixtures/pages-with-i18n/api/shows/[...params].js:
--------------------------------------------------------------------------------
1 | export default async (req, res) => {
2 | // Get the params and query string parameters
3 | const { query } = req;
4 | const { params, ...queryStringParams } = query;
5 |
6 | // Get the ID of the show
7 | const id = params[0];
8 |
9 | // Get the data
10 | const fetchRes = await fetch(`https://api.tvmaze.com/shows/${id}`);
11 | const data = await fetchRes.json();
12 |
13 | // If show was found, return it
14 | if (fetchRes.status == 200) {
15 | res.status(200);
16 | res.json({ params, queryStringParams, show: data });
17 | }
18 | // If show was not found, return error
19 | else {
20 | res.status(404);
21 | res.json({ error: "Show not found" });
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import "./commands";
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/lib/helpers/setupStaticFileForPage.js:
--------------------------------------------------------------------------------
1 | const { copySync } = require("fs-extra");
2 | const { join } = require("path");
3 | const { NEXT_DIST_DIR } = require("../config");
4 |
5 | // Copy the static asset from pages/inputPath to out_publish/outputPath
6 | const setupStaticFileForPage = ({
7 | inputPath,
8 | outputPath = null,
9 | publishPath,
10 | }) => {
11 | // If no outputPath is set, default to the same as inputPath
12 | outputPath = outputPath || inputPath;
13 |
14 | // Perform copy operation
15 | copySync(
16 | join(NEXT_DIST_DIR, "serverless", "pages", inputPath),
17 | join(publishPath, outputPath),
18 | {
19 | overwrite: false,
20 | errorOnExist: true,
21 | }
22 | );
23 | };
24 |
25 | module.exports = setupStaticFileForPage;
26 |
--------------------------------------------------------------------------------
/cypress/fixtures/pages/static.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | const Static = (props) => (
4 |
5 |
6 | This page does not use getInitialProps.
7 |
8 | It is a static page.
9 |
10 | It is never server-side rendered.
11 |
12 | It is served directly by Netlify's CDN.
13 |
14 | The{" "}
15 |
16 | next-on-netlify
17 | {" "}
18 | npm package takes care of deciding which pages to render server-side and
19 | which ones to serve directly via CDN.
20 |
6 | This page does not use getInitialProps.
7 |
8 | It is a static page.
9 |
10 | It is never server-side rendered.
11 |
12 | It is served directly by Netlify's CDN.
13 |
14 | The{" "}
15 |
16 | next-on-netlify
17 | {" "}
18 | npm package takes care of deciding which pages to render server-side and
19 | which ones to serve directly via CDN.
20 |
6 | This page does not use getInitialProps.
7 |
8 | It is a static page.
9 |
10 | It is never server-side rendered.
11 |
12 | It is served directly by Netlify's CDN.
13 |
14 | The{" "}
15 |
16 | next-on-netlify
17 | {" "}
18 | npm package takes care of deciding which pages to render server-side and
19 | which ones to serve directly via CDN.
20 |
6 | This page does not use getInitialProps.
7 |
8 | It is a static page.
9 |
10 | It is never server-side rendered.
11 |
12 | It is served directly by Netlify's CDN.
13 |
14 |
15 | But it has a dynamic URL parameter: /static/:id.
16 |
17 | Try changing the ID. It will always render this page, no matter what you
18 | put.
19 |
20 | I am not sure what this is useful for.
21 |
22 | But it's a feature of NextJS, so... I'm supporting it.
23 |
6 | This page does not use getInitialProps.
7 |
8 | It is a static page.
9 |
10 | It is never server-side rendered.
11 |
12 | It is served directly by Netlify's CDN.
13 |
14 |
15 | But it has a dynamic URL parameter: /static/:id.
16 |
17 | Try changing the ID. It will always render this page, no matter what you
18 | put.
19 |
20 | I am not sure what this is useful for.
21 |
22 | But it's a feature of NextJS, so... I'm supporting it.
23 |
6 | This page does not use getInitialProps.
7 |
8 | It is a static page.
9 |
10 | It is never server-side rendered.
11 |
12 | It is served directly by Netlify's CDN.
13 |
14 |
15 | But it has a dynamic URL parameter: /static/:id.
16 |
17 | Try changing the ID. It will always render this page, no matter what you
18 | put.
19 |
20 | I am not sure what this is useful for.
21 |
22 | But it's a feature of NextJS, so... I'm supporting it.
23 |
31 | );
32 |
33 | export default StaticWithID;
34 |
--------------------------------------------------------------------------------
/lib/pages/getStaticPropsWithRevalidate/pages.js:
--------------------------------------------------------------------------------
1 | const isRouteWithFallback = require("../../helpers/isRouteWithFallback");
2 | const getPrerenderManifest = require("../../helpers/getPrerenderManifest");
3 |
4 | // Collect pages
5 | const pages = [];
6 |
7 | // Get pages using getStaticProps
8 | const { routes } = getPrerenderManifest();
9 |
10 | // Parse pages
11 | Object.entries(routes).forEach(
12 | ([route, { dataRoute, srcRoute, initialRevalidateSeconds }]) => {
13 | // Skip pages without revalidate, these are handled by getStaticProps/pages
14 | if (!initialRevalidateSeconds) return;
15 |
16 | // Skip pages with fallback, these are handled by
17 | // getStaticPropsWithFallback/pages
18 | if (isRouteWithFallback(srcRoute)) return;
19 |
20 | // Add the page
21 | pages.push({
22 | route,
23 | srcRoute,
24 | dataRoute,
25 | });
26 | }
27 | );
28 |
29 | module.exports = pages;
30 |
--------------------------------------------------------------------------------
/lib/helpers/isRouteInPrerenderManifest.js:
--------------------------------------------------------------------------------
1 | const getPrerenderManifest = require("./getPrerenderManifest");
2 | const i18n = require("./getI18n")();
3 |
4 | const { routes, dynamicRoutes } = getPrerenderManifest();
5 | const { defaultLocale, locales } = i18n;
6 |
7 | const isRouteInManifestWithI18n = (route) => {
8 | let isStaticRoute = false;
9 | Object.entries(routes).forEach(([staticRoute, { srcRoute }]) => {
10 | // This is because in i18n we set the nakedRoute to be the srcRoute in the manifest
11 | if (route === srcRoute) isStaticRoute = true;
12 | });
13 | return isStaticRoute || route in dynamicRoutes;
14 | };
15 |
16 | // Return true if the route is defined in the prerender manifest
17 | const isRouteInPrerenderManifest = (route) => {
18 | if (i18n.defaultLocale) return isRouteInManifestWithI18n(route);
19 | return route in routes || route in dynamicRoutes;
20 | };
21 |
22 | module.exports = isRouteInPrerenderManifest;
23 |
--------------------------------------------------------------------------------
/cypress/fixtures/pages/getServerSideProps/static.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | const Show = ({ show }) => (
4 |
5 |
6 | This page uses getInitialProps() to fetch the show with the ID provided in
7 | the URL: /shows/:id
8 |
9 | Refresh the page to see server-side rendering in action.
10 |
11 | You can also try changing the ID to any other number between 1-10000.
12 |
6 | This page uses getServerSideProps() to fetch the show with the ID provided
7 | in the URL: /shows/:id
8 |
9 | Refresh the page to see server-side rendering in action.
10 |
11 | You can also try changing the ID to any other number between 1-10000.
12 |
6 | This page uses getInitialProps() to fetch the show with the ID provided in
7 | the URL: /shows/:id
8 |
9 | Refresh the page to see server-side rendering in action.
10 |
11 | You can also try changing the ID to any other number between 1-10000.
12 |
6 | This page uses getInitialProps() to fetch the show with the ID provided in
7 | the URL: /shows/:id
8 |
9 | Refresh the page to see server-side rendering in action.
10 |
11 | You can also try changing the ID to any other number between 1-10000.
12 |
7 | This page uses getStaticProps() and is SSRed when in preview mode.
8 |
9 |
10 | By default, it shows the TV show by ID (as static HTML).
11 |
12 | But when in preview mode, it shows person by ID instead (SSRed).
13 |
7 | This page uses getStaticProps() and is SSRed when in preview mode.
8 |
9 |
10 | By default, it shows the TV show by ID (as static HTML).
11 |
12 | But when in preview mode, it shows person by ID instead (SSRed).
13 |
18 | );
19 |
20 | export async function getStaticPaths() {
21 | // Set the paths we want to pre-render
22 | const paths = [{ params: { id: "1" } }, { params: { id: "2" } }];
23 |
24 | // We'll pre-render only these paths at build time.
25 | // { fallback: false } means other routes should 404.
26 | return { paths, fallback: true };
27 | }
28 |
29 | export async function getStaticProps({ params }) {
30 | // The ID to render
31 | const { id } = params;
32 |
33 | const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
34 | const data = await res.json();
35 |
36 | return {
37 | props: {
38 | show: data,
39 | },
40 | revalidate: 1,
41 | };
42 | }
43 |
44 | export default Show;
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Finn Woelm
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/fixtures/pages-dynamic-imports/deep/import.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import dynamic from "next/dynamic";
3 | const Header = dynamic(
4 | () => import(/* webpackChunkName: 'header' */ "../../components/Header"),
5 | { ssr: true }
6 | );
7 |
8 | const Show = ({ show }) => (
9 |
10 |
11 |
12 | This page uses getInitialProps() to fetch the show with the ID provided in
13 | the URL: /shows/:id
14 |
15 | Refresh the page to see server-side rendering in action.
16 |
17 | You can also try changing the ID to any other number between 1-10000.
18 |
18 | );
19 |
20 | export async function getStaticPaths() {
21 | // Set the paths we want to pre-render
22 | const paths = [
23 | { params: { id: "3" } },
24 | { params: { id: "4" } },
25 | { params: { id: "75" } },
26 | ];
27 |
28 | // We'll pre-render only these paths at build time.
29 | // { fallback: false } means other routes should 404.
30 | return { paths, fallback: false };
31 | }
32 |
33 | export async function getStaticProps({ params }) {
34 | // The ID to render
35 | const { id } = params;
36 |
37 | const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
38 | const data = await res.json();
39 |
40 | return {
41 | props: {
42 | show: data,
43 | },
44 | revalidate: 1,
45 | };
46 | }
47 |
48 | export default Show;
49 |
--------------------------------------------------------------------------------
/lib/pages/getServerSideProps/redirects.js:
--------------------------------------------------------------------------------
1 | const addLocaleRedirects = require("../../helpers/addLocaleRedirects");
2 | const getNetlifyFunctionName = require("../../helpers/getNetlifyFunctionName");
3 | const getDataRouteForRoute = require("../../helpers/getDataRouteForRoute");
4 | const pages = require("./pages");
5 |
6 | const redirects = [];
7 |
8 | /** getServerSideProps pages
9 | *
10 | * Page params {
11 | * route -> '/ssr', '/ssr/[id]'
12 | * filePath -> 'pages/ssr.js', 'pages/ssr/[id].js'
13 | * }
14 | **/
15 |
16 | pages.forEach(({ route, filePath }) => {
17 | const functionName = getNetlifyFunctionName(filePath);
18 | const target = `/.netlify/functions/${functionName}`;
19 |
20 | addLocaleRedirects(redirects)(route, target);
21 |
22 | // Add one redirect for the naked route
23 | // i.e. /ssr
24 | redirects.push({
25 | route,
26 | target,
27 | });
28 |
29 | // Add one redirect for the data route;
30 | // pages-manifest doesn't provide the dataRoute for us so we
31 | // construct it ourselves with getDataRouteForRoute
32 | redirects.push({
33 | route: getDataRouteForRoute(route),
34 | target,
35 | });
36 | });
37 |
38 | module.exports = redirects;
39 |
--------------------------------------------------------------------------------
/tests/nextConfigFunction.test.js:
--------------------------------------------------------------------------------
1 | // Test next-on-netlify when config is set from a function in next.config.js
2 | // See: https://github.com/netlify/next-on-netlify/issues/25
3 |
4 | const { parse, join } = require("path");
5 | const buildNextApp = require("./helpers/buildNextApp");
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 | // Capture the output to verify successful build
16 | let buildOutput;
17 |
18 | beforeAll(
19 | async () => {
20 | buildOutput = await buildNextApp()
21 | .forTest(__filename)
22 | .withPages("pages-simple")
23 | .withNextConfig("next.config.js-with-function.js")
24 | .withPackageJson("package.json")
25 | .build();
26 | },
27 | // time out after 180 seconds
28 | 180 * 1000
29 | );
30 |
31 | describe("next-on-netlify", () => {
32 | test("builds successfully", () => {
33 | expect(buildOutput).toMatch("Next on Netlify");
34 | expect(buildOutput).toMatch("Success! All done!");
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/lib/templates/netlifyFunction.js:
--------------------------------------------------------------------------------
1 | // TEMPLATE: This file will be copied to the Netlify functions directory when
2 | // running next-on-netlify
3 |
4 | // Render function for the Next.js page
5 | const renderNextPage = require("./renderNextPage");
6 |
7 | exports.handler = async (event, context, callback) => {
8 | // x-forwarded-host is undefined on Netlify for proxied apps that need it
9 | // fixes https://github.com/netlify/next-on-netlify/issues/46
10 | if (!event.multiValueHeaders.hasOwnProperty("x-forwarded-host")) {
11 | event.multiValueHeaders["x-forwarded-host"] = [event.headers["host"]];
12 | }
13 |
14 | // Get the request URL
15 | const { path } = event;
16 | console.log("[request]", path);
17 |
18 | // Render the Next.js page
19 | const response = await renderNextPage({ event, context });
20 |
21 | // Convert header values to string. Netlify does not support integers as
22 | // header values. See: https://github.com/netlify/cli/issues/451
23 | Object.keys(response.multiValueHeaders).forEach((key) => {
24 | response.multiValueHeaders[key] = response.multiValueHeaders[
25 | key
26 | ].map((value) => String(value));
27 | });
28 |
29 | response.multiValueHeaders["Cache-Control"] = ["no-cache"];
30 |
31 | callback(null, response);
32 | };
33 |
--------------------------------------------------------------------------------
/lib/steps/setupHeaders.js:
--------------------------------------------------------------------------------
1 | const { join } = require("path");
2 | const { existsSync, readFileSync, writeFileSync } = require("fs-extra");
3 | const { logTitle, logItem } = require("../helpers/logger");
4 | const { CUSTOM_HEADERS_PATH } = require("../config");
5 |
6 | // Setup _headers file to override specific header rules for next assets
7 | const setupHeaders = (publishPath) => {
8 | logTitle("🔀 Setting up headers");
9 |
10 | // Collect custom redirects defined by the user
11 | const headers = [];
12 | if (existsSync(CUSTOM_HEADERS_PATH)) {
13 | logItem("# Prepending custom headers");
14 | headers.push(readFileSync(CUSTOM_HEADERS_PATH, "utf8"));
15 | }
16 |
17 | // Add NoN section heading
18 | headers.push("# Next-on-Netlify Headers");
19 |
20 | // Add rule to override cache control for static chunks
21 | const indentNewLine = (s) => `\n ${s}`;
22 | const staticChunkRule = [
23 | `/_next/static/chunks/*`,
24 | indentNewLine(`cache-control: public`),
25 | indentNewLine(`cache-control: max-age=31536000`),
26 | indentNewLine(`cache-control: immutable`),
27 | ].join("");
28 | headers.push(staticChunkRule);
29 |
30 | // Write redirects to _redirects file
31 | writeFileSync(join(publishPath, "_headers"), headers.join("\n"));
32 | };
33 |
34 | module.exports = setupHeaders;
35 |
--------------------------------------------------------------------------------
/lib/pages/getInitialProps/pages.js:
--------------------------------------------------------------------------------
1 | const getPagesManifest = require("../../helpers/getPagesManifest");
2 | const isHtmlFile = require("../../helpers/isHtmlFile");
3 | const isFrameworkRoute = require("../../helpers/isFrameworkRoute");
4 | const isApiRoute = require("../../helpers/isApiRoute");
5 | const isRouteInPrerenderManifest = require("../../helpers/isRouteInPrerenderManifest");
6 | const isRouteWithDataRoute = require("../../helpers/isRouteWithDataRoute");
7 |
8 | // Collect pages
9 | const pages = [];
10 |
11 | // Get HTML and SSR pages and API endpoints from the NextJS pages manifest
12 | const pagesManifest = getPagesManifest();
13 |
14 | // Parse pages
15 | Object.entries(pagesManifest).forEach(([route, filePath]) => {
16 | // Ignore HTML files
17 | if (isHtmlFile(filePath)) return;
18 |
19 | // Skip framework pages, such as _app and _error
20 | if (isFrameworkRoute(route)) return;
21 |
22 | // Skip API endpoints
23 | if (isApiRoute(route)) return;
24 |
25 | // Skip page if it is actually used with getStaticProps
26 | if (isRouteInPrerenderManifest(route)) return;
27 |
28 | // Skip page if it has a data route (because then it is a page with
29 | // getServerSideProps)
30 | if (isRouteWithDataRoute(route)) return;
31 |
32 | // Add page
33 | pages.push({ route, filePath });
34 | });
35 |
36 | module.exports = pages;
37 |
--------------------------------------------------------------------------------
/lib/pages/getServerSideProps/pages.js:
--------------------------------------------------------------------------------
1 | const getPagesManifest = require("../../helpers/getPagesManifest");
2 | const isHtmlFile = require("../../helpers/isHtmlFile");
3 | const isFrameworkRoute = require("../../helpers/isFrameworkRoute");
4 | const isApiRoute = require("../../helpers/isApiRoute");
5 | const isRouteInPrerenderManifest = require("../../helpers/isRouteInPrerenderManifest");
6 | const isRouteWithDataRoute = require("../../helpers/isRouteWithDataRoute");
7 |
8 | // Collect pages
9 | const pages = [];
10 |
11 | // Get HTML and SSR pages and API endpoints from the NextJS pages manifest
12 | const pagesManifest = getPagesManifest();
13 |
14 | // Parse pages
15 | Object.entries(pagesManifest).forEach(([route, filePath]) => {
16 | // Ignore HTML files
17 | if (isHtmlFile(filePath)) return;
18 |
19 | // Skip framework pages, such as _app and _error
20 | if (isFrameworkRoute(route)) return;
21 |
22 | // Skip API endpoints
23 | if (isApiRoute(route)) return;
24 |
25 | // Skip page if it is actually used with getStaticProps
26 | if (isRouteInPrerenderManifest(route)) return;
27 |
28 | // Skip page if it has no data route (because then it is a page with
29 | // getInitialProps)
30 | if (!isRouteWithDataRoute(route)) return;
31 |
32 | // Add page
33 | pages.push({ route, filePath });
34 | });
35 |
36 | module.exports = pages;
37 |
--------------------------------------------------------------------------------
/lib/templates/renderNextPage.js:
--------------------------------------------------------------------------------
1 | // Load the NextJS page
2 | const nextPage = require("./nextPage");
3 | const createRequestObject = require("./createRequestObject");
4 | const createResponseObject = require("./createResponseObject");
5 |
6 | // Render the Next.js page
7 | const renderNextPage = ({ event, context }) => {
8 | // The Next.js page is rendered inside a promise that is resolved when the
9 | // Next.js page ends the response via `res.end()`
10 | const promise = new Promise((resolve) => {
11 | // Create a Next.js-compatible request and response object
12 | // These mock the ClientRequest and ServerResponse classes from node http
13 | // See: https://nodejs.org/api/http.html
14 | const req = createRequestObject({ event, context });
15 | const res = createResponseObject({
16 | onResEnd: (response) => resolve(response),
17 | });
18 |
19 | // Check if page is a Next.js page or an API route
20 | const isNextPage = nextPage.render instanceof Function;
21 | const isApiRoute = !isNextPage;
22 |
23 | // Perform the render: render() for Next.js page or default() for API route
24 | if (isNextPage) return nextPage.render(req, res);
25 | if (isApiRoute) return nextPage.default(req, res);
26 | });
27 |
28 | // Return the promise
29 | return promise;
30 | };
31 |
32 | module.exports = renderNextPage;
33 |
--------------------------------------------------------------------------------
/lib/helpers/logger.js:
--------------------------------------------------------------------------------
1 | // Helpers for writing info to console.log
2 | const { program } = require("commander");
3 |
4 | // Number of items processed in current build step
5 | let itemsCount = 0;
6 |
7 | // Format in bold
8 | const logTitle = (...args) => {
9 | // Finish previous log section
10 | finishLogForBuildStep();
11 |
12 | // Print title
13 | log(`\x1b[1m${args.join(" ")}\x1b[22m`);
14 | };
15 |
16 | // Indent by three spaces
17 | const logItem = (...args) => {
18 | // Display item if within max log lines
19 | if (itemsCount < program.maxLogLines) log(" ", ...args);
20 |
21 | itemsCount += 1;
22 | };
23 |
24 | // Just console.log
25 | const log = (...args) => console.log(...args);
26 |
27 | // Finish log section: Write a single line for any suppressed/hidden items
28 | // and reset the item counter
29 | const finishLogForBuildStep = () => {
30 | // Display number of suppressed log lines
31 | if (itemsCount > program.maxLogLines) {
32 | const hiddenLines = itemsCount - program.maxLogLines;
33 | log(
34 | " ",
35 | "+",
36 | hiddenLines,
37 | "more",
38 | "(run next-on-netlify with --max-log-lines XX to",
39 | "show more or fewer lines)"
40 | );
41 | }
42 |
43 | // Reset counter
44 | itemsCount = 0;
45 | };
46 |
47 | module.exports = {
48 | logTitle,
49 | logItem,
50 | log,
51 | };
52 |
--------------------------------------------------------------------------------
/lib/pages/getStaticPropsWithRevalidate/setup.js:
--------------------------------------------------------------------------------
1 | const { join } = require("path");
2 | const { logTitle, logItem } = require("../../helpers/logger");
3 | const getFilePathForRoute = require("../../helpers/getFilePathForRoute");
4 | const setupNetlifyFunctionForPage = require("../../helpers/setupNetlifyFunctionForPage");
5 | const pages = require("./pages");
6 |
7 | // Create a Netlify Function for every page with getStaticProps and revalidate
8 | const setup = (functionsPath) => {
9 | logTitle(
10 | "💫 Setting up pages with getStaticProps and revalidation interval",
11 | "as Netlify Functions in",
12 | functionsPath
13 | );
14 |
15 | // Keep track of the functions that have been set up, so that we do not set up
16 | // a function for the same file path twice
17 | const filePathsDone = [];
18 |
19 | // Create Netlify Function for every page
20 | pages.forEach(({ route, srcRoute }) => {
21 | const relativePath = getFilePathForRoute(srcRoute || route, "js");
22 | const filePath = join("pages", relativePath);
23 |
24 | // Skip if we have already set up a function for this file
25 | if (filePathsDone.includes(filePath)) return;
26 |
27 | // Set up the function
28 | logItem(filePath);
29 | setupNetlifyFunctionForPage({ filePath, functionsPath });
30 | filePathsDone.push(filePath);
31 | });
32 | };
33 |
34 | module.exports = setup;
35 |
--------------------------------------------------------------------------------
/lib/pages/withoutProps/setup.js:
--------------------------------------------------------------------------------
1 | const { join, relative } = require("path");
2 | const { copySync } = require("fs-extra");
3 | const { logTitle, logItem } = require("../../helpers/logger");
4 | const { NEXT_DIST_DIR } = require("../../config");
5 | const i18n = require("../../helpers/getI18n")();
6 | const setupStaticFileForPage = require("../../helpers/setupStaticFileForPage");
7 | const pages = require("./pages");
8 |
9 | // Identify all pages that have been pre-rendered and copy each one to the
10 | // Netlify publish directory.
11 | const setup = (publishPath) => {
12 | logTitle("🔥 Copying pre-rendered pages without props to", publishPath);
13 |
14 | // Copy each page to the Netlify publish directory
15 | pages.forEach(({ filePath }) => {
16 | logItem(filePath);
17 |
18 | // HACK: If i18n, 404.html needs to be at the top level of the publish directory
19 | if (
20 | i18n.defaultLocale &&
21 | filePath === `pages/${i18n.defaultLocale}/404.html`
22 | ) {
23 | copySync(
24 | join(NEXT_DIST_DIR, "serverless", filePath),
25 | join(publishPath, "404.html")
26 | );
27 | }
28 |
29 | // The path to the file, relative to the pages directory
30 | const relativePath = relative("pages", filePath);
31 | setupStaticFileForPage({ inputPath: relativePath, publishPath });
32 | });
33 | };
34 |
35 | module.exports = setup;
36 |
--------------------------------------------------------------------------------
/tests/fixtures/pages/shows/[id].js:
--------------------------------------------------------------------------------
1 | import Error from "next/error";
2 | import Link from "next/link";
3 |
4 | const Show = ({ errorCode, show }) => {
5 | // If show item was not found, render 404 page
6 | if (errorCode) {
7 | return ;
8 | }
9 |
10 | // Otherwise, render show
11 | return (
12 |
13 |
14 | This page uses getInitialProps() to fetch the show with the ID provided
15 | in the URL: /shows/:id
16 |
17 | Refresh the page to see server-side rendering in action.
18 |
19 | You can also try changing the ID to any other number between 1-10000.
20 |
33 | );
34 | };
35 |
36 | Show.getInitialProps = async ({ res: req, query }) => {
37 | // Get the ID to render
38 | const { id } = query;
39 |
40 | // Get the data
41 | const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
42 | const data = await res.json();
43 |
44 | // Set error code if show item could not be found
45 | const errorCode = res.status > 200 ? res.status : false;
46 |
47 | return { errorCode, show: data };
48 | };
49 |
50 | export default Show;
51 |
--------------------------------------------------------------------------------
/cypress/fixtures/pages/shows/[id].js:
--------------------------------------------------------------------------------
1 | import Error from "next/error";
2 | import Link from "next/link";
3 |
4 | const Show = ({ errorCode, show }) => {
5 | // If show item was not found, render 404 page
6 | if (errorCode) {
7 | return ;
8 | }
9 |
10 | // Otherwise, render show
11 | return (
12 |
13 |
14 | This page uses getInitialProps() to fetch the show with the ID provided
15 | in the URL: /shows/:id
16 |
17 | Refresh the page to see server-side rendering in action.
18 |
19 | You can also try changing the ID to any other number between 1-10000.
20 |
33 | );
34 | };
35 |
36 | Show.getInitialProps = async ({ res: req, query }) => {
37 | // Get the ID to render
38 | const { id } = query;
39 |
40 | // Get the data
41 | const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
42 | const data = await res.json();
43 |
44 | // Set error code if show item could not be found
45 | const errorCode = res.status > 200 ? res.status : false;
46 |
47 | return { errorCode, show: data };
48 | };
49 |
50 | export default Show;
51 |
--------------------------------------------------------------------------------
/cypress/fixtures/pages-with-i18n/shows/[id].js:
--------------------------------------------------------------------------------
1 | import Error from "next/error";
2 | import Link from "next/link";
3 |
4 | const Show = ({ errorCode, show }) => {
5 | // If show item was not found, render 404 page
6 | if (errorCode) {
7 | return ;
8 | }
9 |
10 | // Otherwise, render show
11 | return (
12 |
13 |
14 | This page uses getInitialProps() to fetch the show with the ID provided
15 | in the URL: /shows/:id
16 |
17 | Refresh the page to see server-side rendering in action.
18 |
19 | You can also try changing the ID to any other number between 1-10000.
20 |
33 | );
34 | };
35 |
36 | Show.getInitialProps = async ({ res: req, query }) => {
37 | // Get the ID to render
38 | const { id } = query;
39 |
40 | // Get the data
41 | const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
42 | const data = await res.json();
43 |
44 | // Set error code if show item could not be found
45 | const errorCode = res.status > 200 ? res.status : false;
46 |
47 | return { errorCode, show: data };
48 | };
49 |
50 | export default Show;
51 |
--------------------------------------------------------------------------------
/lib/helpers/getSortedRedirects.js:
--------------------------------------------------------------------------------
1 | const {
2 | getSortedRoutes: getSortedRoutesFromNext,
3 | } = require("next/dist/next-server/lib/router/utils/sorted-routes");
4 | const removeFileExtension = require("./removeFileExtension");
5 |
6 | // Return an array of redirects sorted in order of specificity, i.e., more generic
7 | // routes precede more specific ones
8 | const getSortedRedirects = (redirects) => {
9 | // The @sls-next getSortedRoutes does not correctly sort routes with file
10 | // endings (e.g., json), so we remove them before sorting and add them back
11 | // after sorting
12 | const routesWithoutExtensions = redirects.map(({ route }) =>
13 | removeFileExtension(route)
14 | );
15 |
16 | // Sort the "naked" routes
17 | const sortedRoutes = getSortedRoutesFromNext(routesWithoutExtensions);
18 |
19 | // Return original routes in the sorted order
20 | return redirects.sort((a, b) => {
21 | // If routes are different, sort according to Next.js' getSortedRoutes
22 | if (a.route !== b.route) {
23 | return (
24 | sortedRoutes.indexOf(removeFileExtension(a.route)) -
25 | sortedRoutes.indexOf(removeFileExtension(b.route))
26 | );
27 | }
28 | // Otherwise, put the route with more conditions first
29 | return (b.conditions || []).length - (a.conditions || []).length;
30 | });
31 | };
32 |
33 | module.exports = getSortedRedirects;
34 |
--------------------------------------------------------------------------------
/cypress/fixtures/pages/getServerSideProps/[id].js:
--------------------------------------------------------------------------------
1 | import Error from "next/error";
2 | import Link from "next/link";
3 |
4 | const Show = ({ errorCode, show }) => {
5 | // If show item was not found, render 404 page
6 | if (errorCode) {
7 | return ;
8 | }
9 |
10 | // Otherwise, render show
11 | return (
12 |
13 |
14 | This page uses getInitialProps() to fetch the show with the ID provided
15 | in the URL: /shows/:id
16 |
17 | Refresh the page to see server-side rendering in action.
18 |
19 | You can also try changing the ID to any other number between 1-10000.
20 |
33 | );
34 | };
35 |
36 | export const getServerSideProps = async ({ params }) => {
37 | // The ID to render
38 | const { id } = params;
39 |
40 | const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
41 | const data = await res.json();
42 |
43 | // Set error code if show item could not be found
44 | const errorCode = res.status > 200 ? res.status : false;
45 |
46 | return {
47 | props: {
48 | errorCode,
49 | show: data,
50 | },
51 | };
52 | };
53 |
54 | export default Show;
55 |
--------------------------------------------------------------------------------
/tests/fixtures/pages/getServerSideProps/[id].js:
--------------------------------------------------------------------------------
1 | import Error from "next/error";
2 | import Link from "next/link";
3 |
4 | const Show = ({ errorCode, show }) => {
5 | // If show item was not found, render 404 page
6 | if (errorCode) {
7 | return ;
8 | }
9 |
10 | // Otherwise, render show
11 | return (
12 |
13 |
14 | This page uses getInitialProps() to fetch the show with the ID provided
15 | in the URL: /shows/:id
16 |
17 | Refresh the page to see server-side rendering in action.
18 |
19 | You can also try changing the ID to any other number between 1-10000.
20 |
33 | );
34 | };
35 |
36 | export const getServerSideProps = async ({ params }) => {
37 | // The ID to render
38 | const { id } = params;
39 |
40 | const res = await fetch(`https://api.tvmaze.com/shows/${id}`);
41 | const data = await res.json();
42 |
43 | // Set error code if show item could not be found
44 | const errorCode = res.status > 200 ? res.status : false;
45 |
46 | return {
47 | props: {
48 | errorCode,
49 | show: data,
50 | },
51 | };
52 | };
53 |
54 | export default Show;
55 |
--------------------------------------------------------------------------------
/MIGRATING.md:
--------------------------------------------------------------------------------
1 | ## Migrating to Essential Next.js Plugin
2 |
3 | This guide is to assist `next-on-netlify` users in their migration to Netlify's [Essential Next.js Build Plugin](https://github.com/netlify/netlify-plugin-nextjs).
4 |
5 | Please visit [this issue](https://github.com/netlify/next-on-netlify/issues/176) to ask questions about migrating and/or the deprecation of `next-on-netlify`.
6 |
7 | ### Existing NoN users
8 |
9 | 1. [Uninstall](https://docs.npmjs.com/uninstalling-packages-and-dependencies) `next-on-netlify`.
10 | 2. Remove the `"postbuild": "next-on-netlify"` script from your `package.json`.
11 | 3. Get the Essential Next.js plugin.
12 | - For new Next.js sites on Netlify, the plugin will be automatically installed. You can [opt out of the plugin](https://docs.netlify.com/configure-builds/build-plugins/#remove-a-plugin) by visiting the **Plugins** tab for your site in the Netlify UI.
13 | - For existing sites on Netlify, follow the manual installation instructions in the [plugin README](https://github.com/netlify/netlify-plugin-nextjs#installation-and-configuration), which explains UI-based and file-based installation.
14 |
15 | ### Brand New Users
16 |
17 | Deploy a new Next.js site to Netlify! Your site will automatically build with the plugin. You can [opt out of the plugin](https://docs.netlify.com/configure-builds/build-plugins/#remove-a-plugin) by visiting the **Plugins** tab for your site in the Netlify UI.
18 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // Loads a page and removes all Javascript
2 | // Adapted from https://glebbahmutov.com/blog/ssr-e2e/
3 | Cypress.Commands.add("ssr", (url) => {
4 | cy.request(url)
5 | .its("body")
6 | .then((html) => {
7 | // remove all