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

Support for SSG preview mode #50

Merged
merged 6 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ jspm_packages

# OS
.DS_Store

# Local Netlify folder
.netlify
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
- [Custom Netlify Redirects](#custom-netlify-redirects)
- [Custom Netlify Functions](#custom-netlify-functions)
- [Caveats](#caveats)
- [Preview Mode](#preview-mode)
- [Fallbacks for Pages with `getStaticPaths`](#fallbacks-for-pages-with-getstaticpaths)
- [Credits](#credits)
- [Showcase](#showcase)
Expand Down Expand Up @@ -177,7 +176,9 @@ SSR pages and API endpoints. It is currently not possible to create custom Netli

### Preview Mode
lindsaylevine marked this conversation as resolved.
Show resolved Hide resolved

[Next.js Preview Mode](https://nextjs.org/docs/advanced-features/preview-mode) does not work on pages that are pre-rendered (pages with `getStaticProps`). Netlify currently does not support cookie-based redirects, which are needed for supporting preview mode on pre-rendered pages. Preview mode works correctly on any server-side-rendered pages (pages with `getInitialProps` or `getServerSideProps`). See: [Issue #10](https://github.com/netlify/next-on-netlify/issues/10)
Preview Mode is not yet available locally, running `netlify dev`, for static pages without revalidate or fallback. This will be supported soon.

For now, Preview Mode *is* supported in production for all Next.js page types.

### Fallbacks for Pages with `getStaticPaths`

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Page = () => <p>root-level optional-catch-all</p>;

export async function getServerSideProps() {
return {
props: {},
};
}

export default Page;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Static = () => <p>static page</p>;

export async function getStaticProps() {
return {
props: {},
};
}

export default Static;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const Static = () => <p>static page in subfolder</p>;

export async function getStaticPaths() {
return {
paths: [
{
params: {
id: "static",
},
},
{
params: {
id: "test",
},
},
],
fallback: true,
};
}

export async function getStaticProps() {
return {
props: {},
};
}

export default Static;
12 changes: 12 additions & 0 deletions cypress/fixtures/pages/api/enterPreviewStatic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default async function preview(req, res) {
const { query } = req;
const { id } = query;

// Enable Preview Mode by setting the cookies
res.setPreviewData({});

// Redirect to the path from the fetched post
// We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
res.writeHead(307, { Location: `/previewTest/static` });
res.end();
}
5 changes: 5 additions & 0 deletions cypress/fixtures/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ const Index = ({ shows }) => (
<a>previewTest/222</a>
</Link>
</li>
<li>
<Link href="/previewTest/static">
<a>previewTest/static</a>
</Link>
</li>
</ul>

<h1>6. Static Pages Stay Static</h1>
Expand Down
45 changes: 45 additions & 0 deletions cypress/fixtures/pages/previewTest/static.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Link from "next/link";

const StaticTest = ({ number }) => {
return (
<div>
<p>
This page uses getStaticProps() and is SSRed when in preview mode.
<br />
<br />
By default, it shows the TV show by ID (as static HTML).
<br />
But when in preview mode, it shows person by ID instead (SSRed).
</p>

<hr />

<h1>Number: {number}</h1>

<Link href="/">
<a>Go back home</a>
</Link>
</div>
);
};

export const getStaticProps = async ({ preview }) => {
let number;

// In preview mode, use odd number
if (preview) {
number = 3;
}
// In normal mode, use even number
else {
number = 4;
}
lindsaylevine marked this conversation as resolved.
Show resolved Hide resolved

return {
props: {
number,
},
};
};

export default StaticTest;
1 change: 1 addition & 0 deletions cypress/fixtures/public/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a text file
1 change: 1 addition & 0 deletions cypress/fixtures/public/folder/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a text file in a folder
1 change: 1 addition & 0 deletions cypress/fixtures/public/subfolder/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a text file in a subfolder
70 changes: 67 additions & 3 deletions cypress/integration/default_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,12 +529,18 @@ describe("API endpoint", () => {
});

describe("Preview Mode", () => {
it("redirects to preview test page", () => {
it("redirects to preview test page with dynamic route", () => {
cy.visit("/api/enterPreview?id=999");

cy.url().should("include", "/previewTest/999");
});

it("redirects to static preview test page", () => {
cy.visit("/api/enterPreviewStatic");

cy.url().should("include", "/previewTest/static");
});

it("sets cookies on client", () => {
Cypress.Cookies.debug(true);
cy.getCookie("__prerender_bypass").should("not.exist");
Expand All @@ -546,7 +552,18 @@ describe("Preview Mode", () => {
cy.getCookie("__next_preview_data").should("not.be", null);
});

it("renders page in preview mode", () => {
it("sets cookies on client with static redirect", () => {
Cypress.Cookies.debug(true);
lindsaylevine marked this conversation as resolved.
Show resolved Hide resolved
cy.getCookie("__prerender_bypass").should("not.exist");
cy.getCookie("__next_preview_data").should("not.exist");

cy.visit("/api/enterPreviewStatic");

cy.getCookie("__prerender_bypass").should("not.be", null);
cy.getCookie("__next_preview_data").should("not.be", null);
});

it("renders serverSideProps page in preview mode", () => {
cy.visit("/api/enterPreview?id=999");

if (Cypress.env("DEPLOY") === "local") {
Expand All @@ -557,7 +574,15 @@ describe("Preview Mode", () => {
cy.get("p").should("contain", "Sebastian Lacause");
});

it("can move in and out of preview mode", () => {
it("renders staticProps page in preview mode", () => {
// cypress local (aka netlify dev) doesn't support cookie-based redirects
if (Cypress.env("DEPLOY") !== "local") {
cy.visit("/api/enterPreviewStatic");
cy.get("h1").should("contain", "Number: 3");
}
});

it("can move in and out of preview mode for SSRed page", () => {
cy.visit("/api/enterPreview?id=999");

if (Cypress.env("DEPLOY") === "local") {
Expand All @@ -582,6 +607,45 @@ describe("Preview Mode", () => {
cy.get("h1").should("contain", "Show #222");
cy.get("p").should("contain", "Happyland");
});

it("can move in and out of preview mode for static page", () => {
if (Cypress.env("DEPLOY") !== "local") {
cy.visit("/api/enterPreviewStatic");
cy.window().then((w) => (w.noReload = true));

cy.get("h1").should("contain", "Number: 3");

cy.contains("Go back home").click();

// Verify that we're still in preview mode
cy.contains("previewTest/static").click();
cy.get("h1").should("contain", "Number: 3");

cy.window().should("have.property", "noReload", true);

// Exit preview mode
cy.visit("/api/exitPreview");

// TO-DO: test if this is the static html?
// Verify that we're no longer in preview mode
cy.contains("previewTest/static").click();
cy.get("h1").should("contain", "Number: 4");
}
});

it("hits the prerendered html out of preview mode and netlify function in preview mode", () => {
if (Cypress.env("DEPLOY") !== "local") {
cy.request("/previewTest/static").then((response) => {
expect(response.headers["cache-control"]).to.include("public");
});

cy.visit("/api/enterPreviewStatic");

cy.request("/previewTest/static").then((response) => {
expect(response.headers["cache-control"]).to.include("private");
});
}
});
});

describe("pre-rendered HTML pages", () => {
Expand Down
144 changes: 144 additions & 0 deletions cypress/integration/optionalCatchAll_at_root_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
const project = "optionalCatchAll-at-root";

before(() => {
// When changing the base URL within a spec file, Cypress runs the spec twice
// To avoid rebuilding and redeployment on the second run, we check if the
// project has already been deployed.
cy.task("isDeployed").then((isDeployed) => {
// Cancel setup, if already deployed
if (isDeployed) return;

// Clear project folder
cy.task("clearProject", { project });

// Copy NextJS files
cy.task("copyFixture", {
project,
from: "pages-with-optionalCatchAll-at-root",
to: "pages",
});
cy.task("copyFixture", {
project,
from: "next.config.js",
to: "next.config.js",
});

// Copy package.json file
cy.task("copyFixture", {
project,
from: "package.json",
to: "package.json",
});

// Copy Netlify settings
cy.task("copyFixture", {
project,
from: "netlify.toml",
to: "netlify.toml",
});
cy.task("copyFixture", {
project,
from: ".netlify",
to: ".netlify",
});

// Public
cy.task("copyFixture", {
project,
from: "public",
to: "public",
});

// Build
cy.task("buildProject", { project });

// Deploy
cy.task("deployProject", { project }, { timeout: 180 * 1000 });
});

// Set base URL
cy.task("getBaseUrl", { project }).then((url) => {
Cypress.config("baseUrl", url);
});
});

after(() => {
// While the before hook runs twice (it's re-run when the base URL changes),
// the after hook only runs once.
cy.task("clearDeployment");
});

describe("optional-catch-all page at root level", () => {
it("renders on /", () => {
cy.visit("/");
cy.get("p").should("contain", "root-level optional-catch-all");
});

it("renders on /undefined-path", () => {
cy.visit("/undefined-path");
cy.get("p").should("contain", "root-level optional-catch-all");
});

it("renders on /subfolder/page/test", () => {
cy.visit("/subfolder/page/test");
cy.get("p").should("contain", "root-level optional-catch-all");
});
});

describe("pre-rendered page: /static.js", () => {
it("serves /static", () => {
cy.visit("/static");
cy.get("p").should("contain", "static page");
});
});

describe("pre-rendered pages: /subfolder/[id].js", () => {
it("serves /subfolder/static", () => {
cy.visit("/subfolder/static");
cy.get("p").should("contain", "static page in subfolder");
});

it("serves /subfolder/test", () => {
cy.visit("/subfolder/test");
cy.get("p").should("contain", "static page in subfolder");
});

it("serves the pre-rendered HTML (and not the Netlify Function)", () => {
cy.request("/subfolder/static").then((response) => {
expect(response.headers["cache-control"]).to.include("public");
});
});
});

describe("fallback page: /subfolder/[id].js", () => {
it("renders on /subfolder/undefined-path", () => {
cy.visit("/subfolder/undefined-path");
cy.get("p").should("contain", "static page in subfolder");
});

it("server-side-renders the HTML via a Netlify Function", () => {
cy.request("/subfolder/undefined-path").then((response) => {
expect(response.headers["cache-control"]).not.to.include("public");
});
});
});

describe("public assets", () => {
it("serves /file.txt", () => {
cy.request("/file.txt").then((response) => {
expect(response.body).to.include("a text file");
});
});

it("serves /subfolder/file.txt", () => {
cy.request("/subfolder/file.txt").then((response) => {
expect(response.body).to.include("a text file in a subfolder");
});
});

it("serves /folder/file.txt", () => {
cy.request("/folder/file.txt").then((response) => {
expect(response.body).to.include("a text file in a folder");
});
});
});
Loading