Skip to content

Commit

Permalink
feat(lambda-at-edge, nextjs-component, s3-static-assets): support ver… (
Browse files Browse the repository at this point in the history
  • Loading branch information
dphang committed Nov 17, 2020
1 parent d097966 commit 015065d
Show file tree
Hide file tree
Showing 36 changed files with 517 additions and 64 deletions.
Expand Up @@ -419,6 +419,30 @@ describe("Pages Tests", () => {
}
});
});

[
{
path: "/optional-catch-all-ssg-with-fallback/not-prerendered",
param: "not-prerendered"
}
].forEach(({ path, param }) => {
it(`serves but does not cache fallback page, then caches page ${path}`, () => {
// Ensure we hit a new page
const now = Date.now();
const newPath = `${path}-${now}`;

// Verify first request is to fallback page
cy.request(newPath).then((response) => {
expect(response.headers["cache-control"]).to.equal(
"public, max-age=0, s-maxage=0, must-revalidate"
);
expect(response.body).to.contain('<p data-cy="catch"></p>');
});

// TODO: not sure why but I couldn't verify that subsequent request is the prerendered page, Cypress seemed to cache the fallback page response.
// However, verified manually that it works correctly.
});
});
});

describe("Dynamic SSR page", () => {
Expand Down
21 changes: 16 additions & 5 deletions packages/libs/lambda-at-edge/src/build.ts
Expand Up @@ -565,7 +565,11 @@ class Builder {
* Build static assets such as client-side JS, public files, static pages, etc.
* Note that the upload to S3 is done in a separate deploy step.
*/
async buildStaticAssets(routesManifest: RoutesManifest) {
async buildStaticAssets(
defaultBuildManifest: OriginRequestDefaultHandlerManifest,
routesManifest: RoutesManifest
) {
const buildId = defaultBuildManifest.buildId;
const basePath = routesManifest.basePath;
const nextConfigDir = this.nextConfigDir;
const nextStaticDir = this.nextStaticDir;
Expand All @@ -587,6 +591,12 @@ class Builder {
}
};

// Copy BUILD_ID file
const copyBuildId = copyIfExists(
path.join(dotNextDirectory, "BUILD_ID"),
path.join(assetOutputDirectory, withBasePath("BUILD_ID"))
);

const buildStaticFiles = await readDirectoryFiles(
path.join(dotNextDirectory, "static")
);
Expand Down Expand Up @@ -621,7 +631,7 @@ class Builder {
const destination = path.join(
assetOutputDirectory,
withBasePath(
`static-pages/${(relativePageFilePath as string).replace(
`static-pages/${buildId}/${(relativePageFilePath as string).replace(
/^pages\//,
""
)}`
Expand Down Expand Up @@ -665,7 +675,7 @@ class Builder {
);
const destination = path.join(
assetOutputDirectory,
withBasePath(path.join("static-pages", relativePageFilePath))
withBasePath(path.join("static-pages", buildId, relativePageFilePath))
);

return copyIfExists(source, destination);
Expand All @@ -687,7 +697,7 @@ class Builder {

const destination = path.join(
assetOutputDirectory,
withBasePath(path.join("static-pages", fallback))
withBasePath(path.join("static-pages", buildId, fallback))
);

return copyIfExists(source, destination);
Expand Down Expand Up @@ -727,6 +737,7 @@ class Builder {
const staticDirAssets = await buildPublicOrStaticDirectory("static");

return Promise.all([
copyBuildId, // BUILD_ID
...staticFileAssets, // .next/static
...htmlPageAssets, // prerendered html pages
...prerenderManifestJSONPropFileAssets, // SSG json files
Expand Down Expand Up @@ -807,7 +818,7 @@ class Builder {
this.dotNextDir,
"routes-manifest.json"
));
await this.buildStaticAssets(routesManifest);
await this.buildStaticAssets(defaultBuildManifest, routesManifest);
}

/**
Expand Down
28 changes: 17 additions & 11 deletions packages/libs/lambda-at-edge/src/default-handler.ts
Expand Up @@ -346,7 +346,7 @@ const handleOriginRequest = async ({
request.uri = request.uri.replace(basePath, "");
}
} else if (isHTMLPage || hasFallback) {
s3Origin.path = `${basePath}/static-pages`;
s3Origin.path = `${basePath}/static-pages/${manifest.buildId}`;
const pageName = uri === "/" ? "/index" : uri;
request.uri = `${pageName}.html`;
} else if (isDataReq) {
Expand All @@ -356,7 +356,7 @@ const handleOriginRequest = async ({

if (pagePath === "pages/404.html") {
// Request static 404 page from s3
s3Origin.path = `${basePath}/static-pages`;
s3Origin.path = `${basePath}/static-pages/${manifest.buildId}`;
request.uri = pagePath.replace("pages", "");
} else if (
pagePath === "pages/_error.js" ||
Expand Down Expand Up @@ -384,7 +384,7 @@ const handleOriginRequest = async ({
const pagePath = router(manifest)(uri);

if (pagePath.endsWith(".html") && !isPreviewRequest) {
s3Origin.path = `${basePath}/static-pages`;
s3Origin.path = `${basePath}/static-pages/${manifest.buildId}`;
request.uri = pagePath.replace("pages", "");
addS3HostHeader(request, normalisedS3DomainName);

Expand Down Expand Up @@ -511,9 +511,9 @@ const handleOriginResponse = async ({
};
const s3HtmlParams = {
Bucket: bucketName,
Key: `${basePath}${
basePath === "" ? "" : "/"
}static-pages/${request.uri
Key: `${basePath}${basePath === "" ? "" : "/"}static-pages/${
manifest.buildId
}/${request.uri
.replace(`/_next/data/${manifest.buildId}/`, "")
.replace(".json", ".html")}`,
Body: html,
Expand All @@ -537,9 +537,9 @@ const handleOriginResponse = async ({
if (!hasFallback) return response;

// If route has fallback, return that page from S3, otherwise return 404 page
let s3Key = `${basePath}${basePath === "" ? "" : "/"}static-pages${
hasFallback.fallback || "/404.html"
}`;
let s3Key = `${basePath}${basePath === "" ? "" : "/"}static-pages/${
manifest.buildId
}${hasFallback.fallback || "/404.html"}`;

const { GetObjectCommand } = await import(
"@aws-sdk/client-s3/commands/GetObjectCommand"
Expand All @@ -553,7 +553,9 @@ const handleOriginResponse = async ({
Key: s3Key
};

const { Body } = await s3.send(new GetObjectCommand(s3Params));
const { Body, CacheControl } = await s3.send(
new GetObjectCommand(s3Params)
);
bodyString = await getStream.default(Body as Readable);

return {
Expand All @@ -570,7 +572,11 @@ const handleOriginResponse = async ({
"cache-control": [
{
key: "Cache-Control",
value: "public, max-age=0, s-maxage=2678400, must-revalidate"
value:
CacheControl ??
(hasFallback.fallback // Use cache-control from S3 response if possible, otherwise use defaults
? "public, max-age=0, s-maxage=0, must-revalidate" // fallback should never be cached
: "public, max-age=0, s-maxage=2678400, must-revalidate")
}
]
},
Expand Down
9 changes: 8 additions & 1 deletion packages/libs/lambda-at-edge/tests/build/build.test.ts
Expand Up @@ -306,14 +306,21 @@ describe("Builder Tests", () => {
expect(nextStaticFiles).toEqual(["chunks"]);

const staticPagesFiles = await fse.readdir(
join(outputDir, `${ASSETS_DIR}/static-pages`)
join(outputDir, `${ASSETS_DIR}/static-pages/test-build-id`)
);
expect(staticPagesFiles).toEqual([
"about.html",
"contact.html",
"index.html",
"terms.html"
]);

// Check BUILD_ID file
expect(
(
await fse.readFile(join(outputDir, `${ASSETS_DIR}/BUILD_ID`))
).toString()
).toEqual("test-build-id");
});
});
});
Expand Down
@@ -1 +1 @@
test-build-id
test-build-id
Expand Up @@ -75,7 +75,8 @@ describe("Lambda@Edge origin response", () => {
expect(s3Client.send).toHaveBeenCalledWith({
Command: "GetObjectCommand",
Bucket: "my-bucket.s3.amazonaws.com",
Key: "static-pages/tests/prerender-manifest-fallback/[fallback].html"
Key:
"static-pages/build-id/tests/prerender-manifest-fallback/[fallback].html"
});

expect(response).toEqual({
Expand All @@ -85,7 +86,7 @@ describe("Lambda@Edge origin response", () => {
"cache-control": [
{
key: "Cache-Control",
value: "public, max-age=0, s-maxage=2678400, must-revalidate"
value: "public, max-age=0, s-maxage=0, must-revalidate" // Fallback page shouldn't be cached as it will override the path for a just generated SSG page.
}
],
"content-type": [
Expand Down Expand Up @@ -115,7 +116,7 @@ describe("Lambda@Edge origin response", () => {
expect(s3Client.send).toHaveBeenCalledWith({
Command: "GetObjectCommand",
Bucket: "my-bucket.s3.amazonaws.com",
Key: "static-pages/404.html"
Key: "static-pages/build-id/404.html"
});

expect(response).toEqual({
Expand Down Expand Up @@ -180,7 +181,7 @@ describe("Lambda@Edge origin response", () => {
expect(s3Client.send).toHaveBeenNthCalledWith(2, {
Command: "PutObjectCommand",
Bucket: "my-bucket.s3.amazonaws.com",
Key: "static-pages/fallback/not-yet-built.html",
Key: "static-pages/build-id/fallback/not-yet-built.html",
Body: "<div>Rendered Page</div>",
ContentType: "text/html",
CacheControl: "public, max-age=0, s-maxage=2678400, must-revalidate"
Expand Down
Expand Up @@ -142,7 +142,7 @@ describe("Lambda@Edge", () => {
s3: {
authMethod: "origin-access-identity",
domainName: "my-bucket.s3.amazonaws.com",
path: "/basepath/static-pages",
path: "/basepath/static-pages/build-id",
region: "us-east-1"
}
});
Expand Down Expand Up @@ -446,7 +446,7 @@ describe("Lambda@Edge", () => {
s3: {
authMethod: "origin-access-identity",
domainName: "my-bucket.s3.eu-west-1.amazonaws.com",
path: "/basepath/static-pages",
path: "/basepath/static-pages/build-id",
region: "eu-west-1"
}
});
Expand Down Expand Up @@ -541,7 +541,7 @@ describe("Lambda@Edge", () => {
s3: {
authMethod: "origin-access-identity",
domainName: "my-bucket.s3.amazonaws.com",
path: "/basepath/static-pages",
path: "/basepath/static-pages/build-id",
region: "us-east-1"
}
});
Expand Down Expand Up @@ -712,7 +712,7 @@ describe("Lambda@Edge", () => {
s3: {
authMethod: "origin-access-identity",
domainName: "my-bucket.s3.amazonaws.com",
path: "/basepath/static-pages",
path: "/basepath/static-pages/build-id",
region: "us-east-1"
}
});
Expand Down
Expand Up @@ -64,7 +64,7 @@ describe("Lambda@Edge", () => {
s3: {
authMethod: "origin-access-identity",
domainName: "my-bucket.s3.amazonaws.com",
path: "/static-pages",
path: "/static-pages/build-id2",
region: "us-east-1"
}
},
Expand Down
Expand Up @@ -148,7 +148,7 @@ describe("Lambda@Edge", () => {
s3: {
authMethod: "origin-access-identity",
domainName: "my-bucket.s3.amazonaws.com",
path: "/static-pages",
path: "/static-pages/build-id",
region: "us-east-1"
}
});
Expand Down Expand Up @@ -247,7 +247,7 @@ describe("Lambda@Edge", () => {
s3: {
authMethod: "origin-access-identity",
domainName: "my-bucket.s3.amazonaws.com",
path: "/static-pages",
path: "/static-pages/build-id",
region: "us-east-1"
}
});
Expand Down Expand Up @@ -541,7 +541,7 @@ describe("Lambda@Edge", () => {
s3: {
authMethod: "origin-access-identity",
domainName: "my-bucket.s3.eu-west-1.amazonaws.com",
path: "/static-pages",
path: "/static-pages/build-id",
region: "eu-west-1"
}
});
Expand Down Expand Up @@ -766,7 +766,7 @@ describe("Lambda@Edge", () => {
s3: {
authMethod: "origin-access-identity",
domainName: "my-bucket.s3.amazonaws.com",
path: "/static-pages",
path: "/static-pages/build-id",
region: "us-east-1"
}
});
Expand Down
Expand Up @@ -2,8 +2,15 @@ import { Readable } from "stream";

export const mockSend = jest.fn((input) => {
if (input.Command === "GetObjectCommand") {
// Simulate fallback page cache control headers
const isFallback = /\[.*]/.test(input.Key as string);
const cacheControl = isFallback
? "public, max-age=0, s-maxage=0, must-revalidate"
: "public, max-age=0, s-maxage=2678400, must-revalidate";

return {
Body: Readable.from(["S3Body"])
Body: Readable.from(["S3Body"]),
CacheControl: cacheControl
};
} else {
return {};
Expand Down

0 comments on commit 015065d

Please sign in to comment.