diff --git a/lib/allNextJsPages.js b/lib/allNextJsPages.js index a6c1ae8..168ce99 100644 --- a/lib/allNextJsPages.js +++ b/lib/allNextJsPages.js @@ -34,7 +34,8 @@ const getAllPages = () => { return // Skip page if it is actually an SSG page - if(route in staticSsgPages || route in dynamicSsgPages) + const normalizedRoute = route === "/index" ? "/" : route + if(normalizedRoute in staticSsgPages || normalizedRoute in dynamicSsgPages) return // Check if we already have a page pointing to this file @@ -62,7 +63,7 @@ const getAllPages = () => { // Parse SSG pages Object.entries(staticSsgPages).forEach(([ route, { dataRoute }]) => { - pages.push(new Page({ route, type: "ssg", dataRoute })) + pages.push(new Page({ route, type: "ssg", dataRoute, alternativeRoutes: route === "/" ? ["/index"] : [] })) }) Object.entries(dynamicSsgPages).forEach(([ route, { dataRoute, fallback }]) => { // Ignore pages without fallback, these are already handled by the @@ -100,6 +101,18 @@ class Page { return this.type === "ssg-fallback" } + routeFile(ext) { + return `${this.route.replace(/^\/$/, '/index')}${ext}` + } + + get htmlFile() { + return this.routeFile(".html") + } + + get jsonFile() { + return this.routeFile(".json") + } + // Return route and alternative routes as array get routesAsArray() { return [this.route, ...this.alternativeRoutes] diff --git a/lib/setupRedirects.js b/lib/setupRedirects.js index 9b9de6e..e014cab 100644 --- a/lib/setupRedirects.js +++ b/lib/setupRedirects.js @@ -42,7 +42,7 @@ const setupRedirects = () => { } // SSG pages else if (page.isSsg()) { - to = `${page.route}.html` + to = page.htmlFile } // SSG fallback pages (for non pre-rendered paths) else if (page.isSsgFallback()) { diff --git a/lib/setupSsgPages.js b/lib/setupSsgPages.js index 2ee4cba..409ea64 100644 --- a/lib/setupSsgPages.js +++ b/lib/setupSsgPages.js @@ -18,13 +18,13 @@ const setupSsgPages = () => { // Copy pre-rendered SSG pages to Netlify publish folder console.log(" ", "1. Copying pre-rendered SSG pages to", NETLIFY_PUBLISH_PATH) - ssgPages.forEach(({ route }) => { - const filePath = join("pages", `${route}.html`) + ssgPages.forEach(({ htmlFile }) => { + const filePath = join("pages", htmlFile) console.log(" ", " ", filePath) copySync( join(NEXT_DIST_DIR, "serverless", filePath), - join(NETLIFY_PUBLISH_PATH, `${route}.html`), + join(NETLIFY_PUBLISH_PATH, htmlFile), { overwrite: false, errorOnExist: true @@ -36,8 +36,8 @@ const setupSsgPages = () => { const nextDataFolder = join(NETLIFY_PUBLISH_PATH, "_next", "data/") console.log(" ", "2. Copying SSG page data to", nextDataFolder) - ssgPages.forEach(({ route, dataRoute }) => { - const dataPath = join("pages", `${route}.json`) + ssgPages.forEach(({ jsonFile, dataRoute }) => { + const dataPath = join("pages", jsonFile) console.log(" ", " ", dataPath) copySync( diff --git a/package-lock.json b/package-lock.json index 09cde8a..c206ca2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "next-on-netlify", - "version": "1.2.0", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tests/__snapshots__/defaults.test.js.snap b/tests/__snapshots__/defaults.test.js.snap index 05cbf56..e5caa72 100644 --- a/tests/__snapshots__/defaults.test.js.snap +++ b/tests/__snapshots__/defaults.test.js.snap @@ -2,25 +2,25 @@ exports[`Routing creates Netlify redirects 1`] = ` "# Next-on-Netlify Redirects -/api/static /.netlify/functions/next_api_static 200 -/getServerSideProps/static /.netlify/functions/next_getServerSideProps_static 200 -/_next/data/%BUILD_ID%/getServerSideProps/static.json /.netlify/functions/next_getServerSideProps_static 200 -/index /.netlify/functions/next_index 200 / /.netlify/functions/next_index 200 -/static /static.html 200 /404 /404.html 200 +/_next/data/%BUILD_ID%/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 +/_next/data/%BUILD_ID%/getServerSideProps/static.json /.netlify/functions/next_getServerSideProps_static 200 +/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 +/api/shows/* /.netlify/functions/next_api_shows_params 200 +/api/shows/:id /.netlify/functions/next_api_shows_id 200 +/api/static /.netlify/functions/next_api_static 200 +/getServerSideProps/:id /.netlify/functions/next_getServerSideProps_id 200 +/getServerSideProps/static /.netlify/functions/next_getServerSideProps_static 200 /getStaticProps/1 /getStaticProps/1.html 200 /getStaticProps/2 /getStaticProps/2.html 200 /getStaticProps/static /getStaticProps/static.html 200 /getStaticProps/withFallback/3 /getStaticProps/withFallback/3.html 200 /getStaticProps/withFallback/4 /getStaticProps/withFallback/4.html 200 -/api/shows/:id /.netlify/functions/next_api_shows_id 200 -/api/shows/* /.netlify/functions/next_api_shows_params 200 -/getServerSideProps/:id /.netlify/functions/next_getServerSideProps_id 200 -/_next/data/%BUILD_ID%/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 /getStaticProps/withFallback/:id /.netlify/functions/next_getStaticProps_withFallback_id 200 -/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 -/shows/:id /.netlify/functions/next_shows_id 200 +/index /.netlify/functions/next_index 200 /shows/* /.netlify/functions/next_shows_params 200 +/shows/:id /.netlify/functions/next_shows_id 200 +/static /static.html 200 /static/:id /static/[id].html 200" `; diff --git a/tests/defaults.test.js b/tests/defaults.test.js index 792298c..32aea89 100644 --- a/tests/defaults.test.js +++ b/tests/defaults.test.js @@ -172,6 +172,9 @@ describe('Routing',() => { // Replace non-persistent build ID with placeholder redirects = redirects.replace(/\/_next\/data\/[^\/]+\//g, "/_next/data/%BUILD_ID%/") + // Sort contents for a stable comparison + redirects = redirects.split(/\n/).sort().join("\n") + // Check that redirects match expect(redirects).toMatchSnapshot() }) diff --git a/tests/fixtures/pages-with-static-props-index/index.js b/tests/fixtures/pages-with-static-props-index/index.js new file mode 100644 index 0000000..1788cf9 --- /dev/null +++ b/tests/fixtures/pages-with-static-props-index/index.js @@ -0,0 +1,21 @@ +import Link from 'next/link' + +const Page = ({ now }) => ( +
+

Date.now(): {now}

+ + + Static page + +
+) + +export async function getStaticProps(context) { + return { + props: { + now: Date.now() + } + } +} + +export default Page diff --git a/tests/fixtures/pages-with-static-props-index/static/index.js b/tests/fixtures/pages-with-static-props-index/static/index.js new file mode 100644 index 0000000..6f5b320 --- /dev/null +++ b/tests/fixtures/pages-with-static-props-index/static/index.js @@ -0,0 +1,21 @@ +import Link from 'next/link' + +const Page = ({ now }) => ( +
+

Date.now(): {now}

+ + + Index page + +
+) + +export async function getStaticProps(context) { + return { + props: { + now: Date.now() + } + } +} + +export default Page diff --git a/tests/staticIndexPages.test.js b/tests/staticIndexPages.test.js new file mode 100644 index 0000000..3404bbd --- /dev/null +++ b/tests/staticIndexPages.test.js @@ -0,0 +1,110 @@ +// Test that next-on-netlify does not crash when pre-rendering index.js file +// with getStaticProps. + +const { parse, join } = require('path') +const { copySync, emptyDirSync, existsSync, + readdirSync, readFileSync, readJsonSync } = require('fs-extra') +const npmRunBuild = require("./helpers/npmRunBuild") + +// The name of this test file (without extension) +const FILENAME = parse(__filename).name + +// The directory which will be used for testing. +// We simulate a NextJS app within that directory, with pages, and a +// package.json file. +const PROJECT_PATH = join(__dirname, "builds", FILENAME) + +// The directory that contains the fixtures, such as NextJS pages, +// NextJS config, and package.json +const FIXTURE_PATH = join(__dirname, "fixtures") + +// Capture the output of `npm run build` to verify successful build +let BUILD_OUTPUT + +beforeAll( + async () => { + // Clear project directory + emptyDirSync(PROJECT_PATH) + emptyDirSync(join(PROJECT_PATH, "pages")) + + // Copy NextJS pages and config + copySync( + join(FIXTURE_PATH, "pages-with-static-props-index"), + join(PROJECT_PATH, "pages") + ) + copySync( + join(FIXTURE_PATH, "next.config.js"), + join(PROJECT_PATH, "next.config.js") + ) + + // Copy package.json + copySync( + join(FIXTURE_PATH, "package.json"), + join(PROJECT_PATH, "package.json") + ) + + // Invoke `npm run build`: Build Next and run next-on-netlify + const { stdout } = await npmRunBuild({ directory: PROJECT_PATH }) + BUILD_OUTPUT = stdout + }, + // time out after 180 seconds + 180 * 1000 +) + +describe('Next', () => { + test('builds successfully', () => { + // NextJS output + expect(BUILD_OUTPUT).toMatch("Creating an optimized production build...") + expect(BUILD_OUTPUT).toMatch("Automatically optimizing pages...") + expect(BUILD_OUTPUT).toMatch("First Load JS shared by all") + + // Next on Netlify output + expect(BUILD_OUTPUT).toMatch("Next on Netlify") + expect(BUILD_OUTPUT).toMatch("Success! All done!") + }) +}) + +describe('Static Pages', () => { + test('copies static pages to output directory', () => { + const OUTPUT_PATH = join(PROJECT_PATH, "out_publish") + + expect(existsSync(join(OUTPUT_PATH, "index.html"))).toBe(true) + expect(existsSync(join(OUTPUT_PATH, "static.html"))).toBe(true) + }) + + test('copies static assets to out_publish/_next/ directory', () => { + const dirs = readdirSync(join(PROJECT_PATH, "out_publish", "_next", "static")) + + expect(dirs.length).toBe(3) + expect(dirs).toContain("chunks") + expect(dirs).toContain("runtime") + }) +}) + +describe('404 Page', () => { + test('copies 404.html to output directory', () => { + const OUTPUT_PATH = join(PROJECT_PATH, "out_publish") + + expect(existsSync(join(OUTPUT_PATH, "404.html"))).toBe(true) + }) + + // This is required for 404.html to work on netlify-dev + test('copies 404.html to directory root', () => { + expect(existsSync(join(PROJECT_PATH, "404.html"))).toBe(true) + }) +}) + +describe('Routing',() => { + test('creates Netlify redirects', async () => { + // Read _redirects file + const contents = readFileSync(join(PROJECT_PATH, "out_publish", "_redirects")) + + // Convert contents into an array, each line being one element + const redirects = contents.toString().split("\n") + + // Check that routes are present + expect(redirects).toContain("/ /index.html 200") + expect(redirects).toContain("/index /index.html 200") + expect(redirects).toContain("/static /static.html 200") + }) +})