Skip to content
This repository was archived by the owner on Jan 28, 2025. It is now read-only.

fix(lambda-at-edge): fix dynamic route precedence conflicting with fallback pages #714

Merged
merged 2 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ describe("Pages Tests", () => {

cy.visit(path);
cy.location("pathname").should("eq", path);

cy.request(path).then((response) => {
expect(response.body).to.contain("catch-all-ssr");
});
});

["HEAD", "DELETE", "POST", "GET", "OPTIONS", "PUT", "PATCH"].forEach(
Expand All @@ -146,4 +150,70 @@ describe("Pages Tests", () => {
);
});
});

describe("Dynamic SSR page", () => {
[{ path: "/another/1234" }].forEach(({ path }) => {
it(`serves and caches page ${path}`, () => {
cy.ensureRouteNotCached(path);

cy.visit(path);
cy.location("pathname").should("eq", path);

cy.request(path).then((response) => {
expect(response.body).to.contain("dynamic-ssr");
});
});

["HEAD", "DELETE", "POST", "GET", "OPTIONS", "PUT", "PATCH"].forEach(
(method) => {
it(`allows HTTP method for path ${path}: ${method}`, () => {
cy.request({ url: path, method: method }).then((response) => {
expect(response.status).to.equal(200);
});
});
}
);
});

[{ path: "/another/ssg-prioritized-over-dynamic-ssr" }].forEach(
({ path }) => {
it(`serves and caches page ${path}`, () => {
cy.visit(path);

cy.ensureRouteCached(path);

cy.visit(path);
cy.location("pathname").should("eq", path);

cy.request(path).then((response) => {
expect(response.body).to.contain(
"ssg-prioritized-over-dynamic-ssr"
);
});
});

["HEAD", "GET"].forEach((method) => {
it(`allows HTTP method for path ${path}: ${method}`, () => {
cy.request({ url: path, method: method }).then((response) => {
expect(response.status).to.equal(200);
});
});
});

["DELETE", "POST", "OPTIONS", "PUT", "PATCH"].forEach((method) => {
it(`disallows HTTP method for path ${path} with 4xx error: ${method}`, () => {
cy.request({
url: path,
method: method,
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.not.equal(404);
expect(response.status).to.be.at.least(400);
expect(response.status).to.be.lessThan(500);
});
});
});
}
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function CatchAllPage(props: CatchAllPageProps): JSX.Element {
return (
<React.Fragment>
<div>
{`Hello ${props.name}. This is a catch-all SSR page using getServerSideProps(). It also has an image.`}
{`Hello ${props.name}. This is catch-all-ssr, a catch-all SSR page using getServerSideProps(). It also has an image.`}
</div>
<img src={"/app-store-badge.png"} alt={"An image"} />
</React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";
import { NextPageContext } from "next";

type DynamicSSRPageProps = {
name: string;
};

export default function DynamicSSRPage(
props: DynamicSSRPageProps
): JSX.Element {
return (
<React.Fragment>
<div>{`Hello ${props.name}. This is dynamic-ssr, a dynamic SSR page.`}</div>
</React.Fragment>
);
}

export async function getServerSideProps(
ctx: NextPageContext
): Promise<{ props: DynamicSSRPageProps }> {
return {
props: { name: "serverless-next.js" }
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";
import { NextPageContext } from "next";

type SSGPrioritizedOverDynamicSSRPageProps = {
name: string;
};

export default function SSGPrioritizedOverDynamicSSRPage(
props: SSGPrioritizedOverDynamicSSRPageProps
): JSX.Element {
return (
<React.Fragment>
{`Hello ${props.name}! This is ssg-prioritized-over-dynamic-ssr, to test that predefined SSG page is prioritized over dynamic SSR page.`}
</React.Fragment>
);
}

export async function getStaticProps(
ctx: NextPageContext
): Promise<{ props: SSGPrioritizedOverDynamicSSRPageProps }> {
return {
props: { name: "serverless-next.js" }
};
}
55 changes: 54 additions & 1 deletion packages/libs/lambda-at-edge/src/default-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,11 +576,64 @@ const hasFallbackForUri = (
const {
pages: { ssr, html }
} = manifest;
// Non dynamic routes are prioritized over dynamic fallbacks, return false to ensure those get rendered instead
// Non-dynamic routes are prioritized over dynamic fallbacks, return false to ensure those get rendered instead
if (ssr.nonDynamic[uri] || html.nonDynamic[uri]) {
return false;
}

let foundFallback:
| {
routeRegex: string;
fallback: string | false;
dataRoute: string;
dataRouteRegex: string;
}
| undefined = undefined; // for later use to reduce duplicate work

// Dynamic routes that does not have fallback are prioritized over dynamic fallback
const isNonFallbackDynamicRoute = Object.values({
...ssr.dynamic,
...html.dynamic
}).find((dynamicRoute) => {
if (foundFallback) {
return false;
}

const re = new RegExp(dynamicRoute.regex);
const matchesRegex = re.test(uri);

// If any dynamic route matches, check that this isn't one of the fallback routes in prerender manifest
if (matchesRegex) {
const matchesFallbackRoute = Object.keys(
prerenderManifest.dynamicRoutes
).find((prerenderManifestRoute) => {
const fileMatchesPrerenderRoute =
dynamicRoute.file === `pages${prerenderManifestRoute}.js`;

if (fileMatchesPrerenderRoute) {
foundFallback =
prerenderManifest.dynamicRoutes[prerenderManifestRoute];
}

return fileMatchesPrerenderRoute;
});

return !matchesFallbackRoute;
} else {
return false;
}
});

if (isNonFallbackDynamicRoute) {
return false;
}

// If fallback previously found, return it to prevent additional regex matching
if (foundFallback) {
return foundFallback;
}

// Otherwise, try to match fallback against dynamic routes in prerender manifest
return Object.values(prerenderManifest.dynamicRoutes).find((routeConfig) => {
const re = new RegExp(routeConfig.routeRegex);
return re.test(uri);
Expand Down