diff --git a/cypress/fixtures/pages-with-optionalCatchAll-at-root/[bar]/ssr.js b/cypress/fixtures/pages-with-optionalCatchAll-at-root/[bar]/ssr.js new file mode 100644 index 0000000..404178e --- /dev/null +++ b/cypress/fixtures/pages-with-optionalCatchAll-at-root/[bar]/ssr.js @@ -0,0 +1,44 @@ +import Error from "next/error"; +import Link from "next/link"; + +const Show = (props) => { + const { errorCode, show } = props; + // If show item was not found, render 404 page + if (errorCode) { + return ; + } + + // Otherwise, render show + return ( +
+

Show #{show.id}

+

{show.name}

+ +
+ + + Go back home to base page + +
+ ); +}; + +export const getServerSideProps = async ({ params }) => { + // The ID to render + const { bar } = params; + + const res = await fetch(`https://api.tvmaze.com/shows/${bar}`); + const data = await res.json(); + + // Set error code if show item could not be found + const errorCode = res.status > 200 ? res.status : false; + + return { + props: { + errorCode, + show: data, + }, + }; +}; + +export default Show; diff --git a/cypress/fixtures/pages-with-optionalCatchAll-at-root/home.js b/cypress/fixtures/pages-with-optionalCatchAll-at-root/home.js new file mode 100644 index 0000000..36c0f0d --- /dev/null +++ b/cypress/fixtures/pages-with-optionalCatchAll-at-root/home.js @@ -0,0 +1,22 @@ +import Link from "next/link"; + +const Home = () => ( +
+

NextJS on Netlify

+ + +
+); + +export default Home; diff --git a/cypress/integration/optionalCatchAll_at_root_spec.js b/cypress/integration/optionalCatchAll_at_root_spec.js index 268df93..8c2ba56 100644 --- a/cypress/integration/optionalCatchAll_at_root_spec.js +++ b/cypress/integration/optionalCatchAll_at_root_spec.js @@ -92,6 +92,42 @@ describe("pre-rendered page: /static.js", () => { }); }); +describe("SSR'd page: /[bar]/ssr.js", () => { + it("loads TV show", () => { + cy.visit("/1337/ssr"); + + cy.get("h1").should("contain", "Show #1337"); + cy.get("p").should("contain", "Whodunnit?"); + }); + + it("loads TV show when SSR-ing", () => { + cy.ssr("/1337/ssr"); + + cy.get("h1").should("contain", "Show #1337"); + cy.get("p").should("contain", "Whodunnit?"); + }); + + it("loads page props from data .json file when navigating to it", () => { + cy.visit("/home"); + cy.window().then((w) => (w.noReload = true)); + + // Navigate to page and test that no reload is performed + // See: https://glebbahmutov.com/blog/detect-page-reload/ + cy.contains("1337/ssr").click(); + + cy.get("h1").should("contain", "Show #1337"); + cy.get("p").should("contain", "Whodunnit?"); + + cy.contains("Go back home").click(); + cy.contains("1338/ssr").click(); + + cy.get("h1").should("contain", "Show #1338"); + cy.get("p").should("contain", "The Whole Truth"); + + cy.window().should("have.property", "noReload", true); + }); +}); + describe("pre-rendered pages: /subfolder/[id].js", () => { it("serves /subfolder/static", () => { cy.visit("/subfolder/static"); diff --git a/lib/helpers/getSortedRedirects.js b/lib/helpers/getSortedRedirects.js index b31b016..dc6463e 100644 --- a/lib/helpers/getSortedRedirects.js +++ b/lib/helpers/getSortedRedirects.js @@ -1,9 +1,7 @@ const { getSortedRoutes: getSortedRoutesFromNext, } = require("next/dist/next-server/lib/router/utils/sorted-routes"); - -// Remove the file extension form the route -const removeFileExtension = (route) => route.replace(/\.[a-zA-Z]+$/, ""); +const removeFileExtension = require("./removeFileExtension"); // Return an array of redirects sorted in order of specificity, i.e., more generic // routes precede more specific ones diff --git a/lib/helpers/removeFileExtension.js b/lib/helpers/removeFileExtension.js new file mode 100644 index 0000000..c504bd7 --- /dev/null +++ b/lib/helpers/removeFileExtension.js @@ -0,0 +1,4 @@ +// Remove the file extension form the route +const removeFileExtension = (route) => route.replace(/\.[a-zA-Z]+$/, ""); + +module.exports = removeFileExtension; diff --git a/lib/steps/copyNextAssets.js b/lib/steps/copyNextAssets.js index 9b946f2..1e23faa 100644 --- a/lib/steps/copyNextAssets.js +++ b/lib/steps/copyNextAssets.js @@ -8,7 +8,9 @@ const { NEXT_DIST_DIR } = require("../config"); const copyNextAssets = (publishPath) => { const staticAssetsPath = join(NEXT_DIST_DIR, "static"); if (!existsSync(staticAssetsPath)) { - throw new Error("No static assets found in .next dist (aka no /.next/static). Please check your project configuration. Your next.config.js must be one of `serverless` or `experimental-serverless-trace`. Your build command should include `next build`."); + throw new Error( + "No static assets found in .next dist (aka no /.next/static). Please check your project configuration. Your next.config.js must be one of `serverless` or `experimental-serverless-trace`. Your build command should include `next build`." + ); } logTitle("💼 Copying static NextJS assets to", publishPath); copySync(staticAssetsPath, join(publishPath, "_next", "static"), { diff --git a/lib/steps/setupRedirects.js b/lib/steps/setupRedirects.js index 355b7a2..e70c8f3 100644 --- a/lib/steps/setupRedirects.js +++ b/lib/steps/setupRedirects.js @@ -9,6 +9,7 @@ const getSortedRedirects = require("../helpers/getSortedRedirects"); const getNetlifyRoutes = require("../helpers/getNetlifyRoutes"); const isRootCatchAllRedirect = require("../helpers/isRootCatchAllRedirect"); const isDynamicRoute = require("../helpers/isDynamicRoute"); +const removeFileExtension = require("../helpers/removeFileExtension"); // Setup _redirects file that routes all requests to the appropriate location, // such as one of the Netlify functions or one of the static files. @@ -37,10 +38,10 @@ const setupRedirects = (publishPath) => { redirects.push("# Next-on-Netlify Redirects"); const staticRedirects = nextRedirects.filter( - ({ route }) => !isDynamicRoute(route) + ({ route }) => !isDynamicRoute(removeFileExtension(route)) ); const dynamicRedirects = nextRedirects.filter(({ route }) => - isDynamicRoute(route) + isDynamicRoute(removeFileExtension(route)) ); // Add next/image redirect to our image function diff --git a/tests/__snapshots__/defaults.test.js.snap b/tests/__snapshots__/defaults.test.js.snap index 9e08179..62f0900 100644 --- a/tests/__snapshots__/defaults.test.js.snap +++ b/tests/__snapshots__/defaults.test.js.snap @@ -11,10 +11,7 @@ exports[`Headers creates Netlify headers 1`] = ` exports[`Routing creates Netlify redirects 1`] = ` "# Next-on-Netlify Redirects / /.netlify/functions/next_index 200 -/_next/data/%BUILD_ID%/getServerSideProps/all.json /.netlify/functions/next_getServerSideProps_all_slug 200 -/_next/data/%BUILD_ID%/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200 /_next/data/%BUILD_ID%/getServerSideProps/static.json /.netlify/functions/next_getServerSideProps_static 200 -/_next/data/%BUILD_ID%/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 /_next/data/%BUILD_ID%/getStaticProps/1.json /.netlify/functions/next_getStaticProps_id 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/getStaticProps/2.json /.netlify/functions/next_getStaticProps_id 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/getStaticProps/static.json /.netlify/functions/next_getStaticProps_static 200! Cookie=__prerender_bypass,__next_preview_data @@ -23,14 +20,10 @@ exports[`Routing creates Netlify redirects 1`] = ` /_next/data/%BUILD_ID%/getStaticProps/withFallback/4.json /.netlify/functions/next_getStaticProps_withFallback_id 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/getStaticProps/withFallback/my/path/1.json /.netlify/functions/next_getStaticProps_withFallback_slug 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/getStaticProps/withFallback/my/path/2.json /.netlify/functions/next_getStaticProps_withFallback_slug 200! Cookie=__prerender_bypass,__next_preview_data -/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 -/_next/data/%BUILD_ID%/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200 /_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/3.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/4.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200! Cookie=__prerender_bypass,__next_preview_data -/_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 /_next/data/%BUILD_ID%/getStaticProps/withRevalidate/1.json /.netlify/functions/next_getStaticProps_withRevalidate_id 200 /_next/data/%BUILD_ID%/getStaticProps/withRevalidate/2.json /.netlify/functions/next_getStaticProps_withRevalidate_id 200 -/_next/data/%BUILD_ID%/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200 /api/static /.netlify/functions/next_api_static 200 /getServerSideProps/static /.netlify/functions/next_getServerSideProps_static 200 /getStaticProps/1 /.netlify/functions/next_getStaticProps_id 200! Cookie=__prerender_bypass,__next_preview_data @@ -45,6 +38,13 @@ exports[`Routing creates Netlify redirects 1`] = ` /getStaticProps/withFallbackBlocking/4 /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200! Cookie=__prerender_bypass,__next_preview_data /getStaticProps/withRevalidate/1 /.netlify/functions/next_getStaticProps_withRevalidate_id 200 /getStaticProps/withRevalidate/2 /.netlify/functions/next_getStaticProps_withRevalidate_id 200 +/_next/data/%BUILD_ID%/getServerSideProps/all.json /.netlify/functions/next_getServerSideProps_all_slug 200 +/_next/data/%BUILD_ID%/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200 +/_next/data/%BUILD_ID%/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 +/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 +/_next/data/%BUILD_ID%/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200 +/_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 +/_next/data/%BUILD_ID%/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200 /_next/image* url=:url w=:width q=:quality /.netlify/functions/next_image?url=:url&w=:width&q=:quality 200 /api/shows/:id /.netlify/functions/next_api_shows_id 200 /api/shows/:params/* /.netlify/functions/next_api_shows_params 200 diff --git a/tests/__snapshots__/i18n.test.js.snap b/tests/__snapshots__/i18n.test.js.snap index 52b9086..b1f4d55 100644 --- a/tests/__snapshots__/i18n.test.js.snap +++ b/tests/__snapshots__/i18n.test.js.snap @@ -5,10 +5,7 @@ exports[`Routing creates Netlify redirects 1`] = ` / /.netlify/functions/next_index 200 /404 /en/404.html 200 /_next/data/%BUILD_ID%/en.json /.netlify/functions/next_index 200 -/_next/data/%BUILD_ID%/en/getServerSideProps/all.json /.netlify/functions/next_getServerSideProps_all_slug 200 -/_next/data/%BUILD_ID%/en/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200 /_next/data/%BUILD_ID%/en/getServerSideProps/static.json /.netlify/functions/next_getServerSideProps_static 200 -/_next/data/%BUILD_ID%/en/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 /_next/data/%BUILD_ID%/en/getStaticProps/1.json /.netlify/functions/next_getStaticProps_id 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/en/getStaticProps/2.json /.netlify/functions/next_getStaticProps_id 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/en/getStaticProps/static.json /.netlify/functions/next_getStaticProps_static 200! Cookie=__prerender_bypass,__next_preview_data @@ -17,37 +14,15 @@ exports[`Routing creates Netlify redirects 1`] = ` /_next/data/%BUILD_ID%/en/getStaticProps/withFallback/4.json /.netlify/functions/next_getStaticProps_withFallback_id 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/en/getStaticProps/withFallback/my/path/1.json /.netlify/functions/next_getStaticProps_withFallback_slug 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/en/getStaticProps/withFallback/my/path/2.json /.netlify/functions/next_getStaticProps_withFallback_slug 200! Cookie=__prerender_bypass,__next_preview_data -/_next/data/%BUILD_ID%/en/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 -/_next/data/%BUILD_ID%/en/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200 /_next/data/%BUILD_ID%/en/getStaticProps/withFallbackBlocking/3.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/en/getStaticProps/withFallbackBlocking/4.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200! Cookie=__prerender_bypass,__next_preview_data -/_next/data/%BUILD_ID%/en/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 /_next/data/%BUILD_ID%/en/getStaticProps/withRevalidate/1.json /.netlify/functions/next_getStaticProps_withRevalidate_id 200 /_next/data/%BUILD_ID%/en/getStaticProps/withRevalidate/2.json /.netlify/functions/next_getStaticProps_withRevalidate_id 200 -/_next/data/%BUILD_ID%/en/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200 -/_next/data/%BUILD_ID%/en/shows/:id.json /.netlify/functions/next_shows_id 200 -/_next/data/%BUILD_ID%/en/shows/:params/* /.netlify/functions/next_shows_params 200 /_next/data/%BUILD_ID%/es.json /.netlify/functions/next_index 200 -/_next/data/%BUILD_ID%/es/getServerSideProps/all.json /.netlify/functions/next_getServerSideProps_all_slug 200 -/_next/data/%BUILD_ID%/es/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200 /_next/data/%BUILD_ID%/es/getServerSideProps/static.json /.netlify/functions/next_getServerSideProps_static 200 -/_next/data/%BUILD_ID%/es/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 /_next/data/%BUILD_ID%/es/getStaticProps/static.json /.netlify/functions/next_getStaticProps_static 200! Cookie=__prerender_bypass,__next_preview_data /_next/data/%BUILD_ID%/es/getStaticProps/with-revalidate.json /.netlify/functions/next_getStaticProps_withrevalidate 200 -/_next/data/%BUILD_ID%/es/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 -/_next/data/%BUILD_ID%/es/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200 -/_next/data/%BUILD_ID%/es/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 -/_next/data/%BUILD_ID%/es/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200 -/_next/data/%BUILD_ID%/es/shows/:id.json /.netlify/functions/next_shows_id 200 -/_next/data/%BUILD_ID%/es/shows/:params/* /.netlify/functions/next_shows_params 200 -/_next/data/%BUILD_ID%/getServerSideProps/all.json /.netlify/functions/next_getServerSideProps_all_slug 200 -/_next/data/%BUILD_ID%/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200 /_next/data/%BUILD_ID%/getServerSideProps/static.json /.netlify/functions/next_getServerSideProps_static 200 -/_next/data/%BUILD_ID%/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 -/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 -/_next/data/%BUILD_ID%/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200 -/_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 -/_next/data/%BUILD_ID%/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200 /api/static /.netlify/functions/next_api_static 200 /en /.netlify/functions/next_index 200 /en/getServerSideProps/static /.netlify/functions/next_getServerSideProps_static 200 @@ -90,6 +65,31 @@ exports[`Routing creates Netlify redirects 1`] = ` /getStaticProps/withRevalidate/1 /.netlify/functions/next_getStaticProps_withRevalidate_id 200 /getStaticProps/withRevalidate/2 /.netlify/functions/next_getStaticProps_withRevalidate_id 200 /static /en/static.html 200 +/_next/data/%BUILD_ID%/en/getServerSideProps/all.json /.netlify/functions/next_getServerSideProps_all_slug 200 +/_next/data/%BUILD_ID%/en/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200 +/_next/data/%BUILD_ID%/en/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 +/_next/data/%BUILD_ID%/en/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 +/_next/data/%BUILD_ID%/en/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200 +/_next/data/%BUILD_ID%/en/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 +/_next/data/%BUILD_ID%/en/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200 +/_next/data/%BUILD_ID%/en/shows/:id.json /.netlify/functions/next_shows_id 200 +/_next/data/%BUILD_ID%/en/shows/:params/* /.netlify/functions/next_shows_params 200 +/_next/data/%BUILD_ID%/es/getServerSideProps/all.json /.netlify/functions/next_getServerSideProps_all_slug 200 +/_next/data/%BUILD_ID%/es/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200 +/_next/data/%BUILD_ID%/es/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 +/_next/data/%BUILD_ID%/es/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 +/_next/data/%BUILD_ID%/es/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200 +/_next/data/%BUILD_ID%/es/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 +/_next/data/%BUILD_ID%/es/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200 +/_next/data/%BUILD_ID%/es/shows/:id.json /.netlify/functions/next_shows_id 200 +/_next/data/%BUILD_ID%/es/shows/:params/* /.netlify/functions/next_shows_params 200 +/_next/data/%BUILD_ID%/getServerSideProps/all.json /.netlify/functions/next_getServerSideProps_all_slug 200 +/_next/data/%BUILD_ID%/getServerSideProps/all/* /.netlify/functions/next_getServerSideProps_all_slug 200 +/_next/data/%BUILD_ID%/getServerSideProps/:id.json /.netlify/functions/next_getServerSideProps_id 200 +/_next/data/%BUILD_ID%/getStaticProps/withFallback/:id.json /.netlify/functions/next_getStaticProps_withFallback_id 200 +/_next/data/%BUILD_ID%/getStaticProps/withFallback/:slug/* /.netlify/functions/next_getStaticProps_withFallback_slug 200 +/_next/data/%BUILD_ID%/getStaticProps/withFallbackBlocking/:id.json /.netlify/functions/next_getStaticProps_withFallbackBlocking_id 200 +/_next/data/%BUILD_ID%/getStaticProps/withRevalidate/withFallback/:id.json /.netlify/functions/next_getStaticProps_withRevalidate_withFallback_id 200 /_next/image* url=:url w=:width q=:quality /.netlify/functions/next_image?url=:url&w=:width&q=:quality 200 /api/shows/:id /.netlify/functions/next_api_shows_id 200 /api/shows/:params/* /.netlify/functions/next_api_shows_params 200 diff --git a/tests/__snapshots__/optionalCatchAll.test.js.snap b/tests/__snapshots__/optionalCatchAll.test.js.snap index 095bb51..8958cfb 100644 --- a/tests/__snapshots__/optionalCatchAll.test.js.snap +++ b/tests/__snapshots__/optionalCatchAll.test.js.snap @@ -3,9 +3,9 @@ exports[`Routing creates Netlify redirects 1`] = ` "# Next-on-Netlify Redirects /_next/data/%BUILD_ID%/page.json /.netlify/functions/next_page 200 +/page /.netlify/functions/next_page 200 /_next/data/%BUILD_ID%/index.json /.netlify/functions/next_all 200 /_next/data/%BUILD_ID%/* /.netlify/functions/next_all 200 -/page /.netlify/functions/next_page 200 /_next/image* url=:url w=:width q=:quality /.netlify/functions/next_image?url=:url&w=:width&q=:quality 200 / /.netlify/functions/next_all 200 /_next/* /_next/:splat 200