/* 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); }); } } });