Skip to content

Instantly share code, notes, and snippets.

@susisu
Created January 29, 2023 11:03
Show Gist options
  • Save susisu/3d60fd13ef449a8f6bbdb939177de440 to your computer and use it in GitHub Desktop.
Save susisu/3d60fd13ef449a8f6bbdb939177de440 to your computer and use it in GitHub Desktop.
CloudFront Function for websites using CloudFront + S3 + Next.js static export (as of v13)
/* eslint-disable no-var, vars-on-top, no-param-reassign */
function redirect(uri) {
// remove repeated slashes
uri = uri.replace(/\/+/g, "/");
// remove trailing slash
if (uri !== "/" && uri.endsWith("/")) {
uri = uri.slice(0, -1);
}
return uri;
}
function route(uri) {
// use original paths under /_next/
if (uri.startsWith("/_next/")) {
return uri;
}
// rewrite root path / and paths like /index
if (uri === "/index" || uri.startsWith("/index/")) {
uri = "/index" + uri;
} else if (uri === "/") {
uri = "/index";
}
// add extension .html if necessary
var filename = uri.split("/").pop();
if (!filename.includes(".") || filename.endsWith(".html")) {
uri = uri + ".html";
}
return uri;
}
function handler(event) {
var request = event.request;
var uri = request.uri;
var redirectUri = redirect(uri);
if (redirectUri !== uri) {
return {
statusCode: 308,
statusDescription: "Permanent Redirect",
headers: {
location: {
value: redirectUri,
},
},
};
}
var routeUri = route(uri);
request.uri = routeUri;
return request;
}
/* eslint-enable no-var, vars-on-top, no-param-reassign */
import { describe, it } from "node:test";
import assert from "node:assert/strict";
describe("handler", () => {
// References:
// - https://github.com/vercel/next.js/blob/6b5f5d2f9f28da36d7fc9a6026e8b5d263cbe475/packages/next/src/shared/lib/page-path/normalize-page-path.ts#L5-L34
// - https://github.com/vercel/next.js/blob/6b5f5d2f9f28da36d7fc9a6026e8b5d263cbe475/packages/next/src/server/base-server.ts#L477-L485
// - https://github.com/vercel/next.js/blob/6b5f5d2f9f28da36d7fc9a6026e8b5d263cbe475/packages/next/src/shared/lib/utils.ts#L332-L344
const tests = [
["/", "/index.html", false],
["/index", "/index/index.html", false],
["/index/foo", "/index/index/foo.html", false],
["/foo/index", "/foo/index.html", false],
["/foo/index/bar", "/foo/index/bar.html", false],
["/foo/bar", "/foo/bar.html", false],
["/foo/bar.html", "/foo/bar.html.html", false],
["/foo/bar.png", "/foo/bar.png", false],
["/foo/bar/", "/foo/bar", true],
["/foo/bar.html/", "/foo/bar.html", true],
["/foo/bar.png/", "/foo/bar.png", true],
["///", "/", true],
["///foo////bar//", "/foo/bar", true],
["///foo////bar.html//", "/foo/bar.html", true],
["///foo////bar.png//", "/foo/bar.png", true],
["/_next/foo/bar", "/_next/foo/bar", false],
["/_next/foo/bar.html", "/_next/foo/bar.html", false],
["/_next/foo/bar.js", "/_next/foo/bar.js", false],
["/_next/foo/bar/", "/_next/foo/bar", true],
["/_next/foo/bar.html/", "/_next/foo/bar.html", true],
["/_next/foo/bar.js/", "/_next/foo/bar.js", true],
["//_next///foo////bar//", "/_next/foo/bar", true],
["//_next///foo////bar.html//", "/_next/foo/bar.html", true],
["//_next///foo////bar.js//", "/_next/foo/bar.js", true],
];
const createGetRequest = (uri) => ({
method: "GET",
uri,
querystring: {},
headers: {},
cookies: {},
});
const createRedirectResponse = (uri) => ({
statusCode: 308,
statusDescription: "Permanent Redirect",
headers: {
location: {
value: uri,
},
},
});
const createGetRequestEvent = (uri) => ({
version: "1.0",
context: {
distributionDomainName: "d123.cloudfront.net",
distributionId: "E123",
eventType: "viewer-request",
requestId: "123",
},
viewer: { ip: "1.2.3.4" },
request: createGetRequest(uri),
});
for (const [inUri, outUri, redirect] of tests) {
if (redirect) {
it(`GET ${inUri} => 308 Permanent Redirect ${outUri}`, () => {
const event = createGetRequestEvent(inUri);
const actual = handler(event);
const expected = createRedirectResponse(outUri);
assert.deepEqual(actual, expected);
});
} else {
it(`GET ${inUri} => GET ${outUri}`, () => {
const event = createGetRequestEvent(inUri);
const actual = handler(event);
const expected = createGetRequest(outUri);
assert.deepEqual(actual, expected);
});
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment