Skip to content

Commit

Permalink
feat(core, nextjs-component): improve routing and support cross rewri…
Browse files Browse the repository at this point in the history
…tes for consolidated API pages in default lambda (#1693)
  • Loading branch information
dphang committed Sep 15, 2021
1 parent c5859c7 commit f058d51
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ describe("Rewrites Tests", () => {
path: "/no-op-rewrite",
expectedRewrite: "/ssr-page",
expectedStatus: 200
},
{
// rewrite from pages -> api
path: "/cross-rewrite",
expectedRewrite: "/api/basic-api",
expectedStatus: 200
},
{
// rewrite from api -> pages
path: "/api/cross-rewrite",
expectedRewrite: "/ssr-page",
expectedStatus: 200
}
].forEach(({ path, expectedRewrite, expectedStatus }) => {
it(`rewrites path ${path} to ${expectedRewrite}`, () => {
Expand Down
8 changes: 8 additions & 0 deletions packages/e2e-tests/next-app-experimental/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ module.exports = {
{
source: "/no-op-rewrite",
destination: "/ssr-page"
},
{
source: "/cross-rewrite",
destination: "/api/basic-api"
},
{
source: "/api/cross-rewrite",
destination: "/ssr-page"
}
];
},
Expand Down
23 changes: 18 additions & 5 deletions packages/libs/core/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const prepareBuildManifests = async (
buildId,
domainRedirects: unnormalisedDomainRedirects
} = buildOptions;

const separateApiLambda = buildOptions.separateApiLambda ?? true;
const domainRedirects = normaliseDomainRedirects(unnormalisedDomainRedirects);

const pageManifest: PageManifest = {
Expand All @@ -53,7 +55,8 @@ export const prepareBuildManifests = async (
publicFiles: {},
trailingSlash: nextConfig?.trailingSlash ?? false,
domainRedirects,
authentication
authentication,
hasApiPages: false
};

const apiManifest: ApiManifest = {
Expand All @@ -72,7 +75,12 @@ export const prepareBuildManifests = async (
const dynamicApi: DynamicPageKeyValue = {};

const isHtmlPage = (path: string): boolean => path.endsWith(".html");
const isApiPage = (path: string): boolean => path.startsWith("pages/api");

// If we are using separate API lambda then API pages go into their own manifest. Otherwise they can be added to
// default manifest as well
const isApiPage = (path: string): boolean => {
return path.startsWith("pages/api");
};

Object.entries(pagesManifest).forEach(([route, pageFile]) => {
// Check for optional catch all dynamic routes vs. other types of dynamic routes
Expand All @@ -86,6 +94,11 @@ export const prepareBuildManifests = async (
? route.split("/[[")[0] || "/"
: "";

// To easily track whether default handler has any API pages
if (!pageManifest.hasApiPages && isApiPage(pageFile)) {
pageManifest.hasApiPages = true;
}

if (isHtmlPage(pageFile)) {
if (isOtherDynamicRoute) {
htmlPages.dynamic[route] = pageFile;
Expand All @@ -95,7 +108,8 @@ export const prepareBuildManifests = async (
} else {
htmlPages.nonDynamic[route] = pageFile;
}
} else if (isApiPage(pageFile)) {
} else if (separateApiLambda && isApiPage(pageFile)) {
// We only want to put API pages in a separate manifest when separateApiLambda is set to true
if (isOtherDynamicRoute) {
dynamicApi[route] = {
file: pageFile,
Expand Down Expand Up @@ -149,8 +163,7 @@ export const prepareBuildManifests = async (
// Include only SSR routes that are in runtime use
const ssrPages = (pageManifest.pages.ssr = usedSSR(
pageManifest,
routesManifest,
apiPages.dynamic.length > 0 || Object.keys(apiPages.nonDynamic).length > 0
routesManifest
));

// Duplicate unlocalized routes for all specified locales.
Expand Down
9 changes: 4 additions & 5 deletions packages/libs/core/src/build/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,14 @@ const filterNonDynamic = (
};

/*
* Keeps only requires SSR pages.
* Keeps only required SSR pages.
*/
export const usedSSR = (
manifest: PageManifest,
routesManifest: RoutesManifest,
hasApi: boolean
routesManifest: RoutesManifest
) => {
// Preview mode means everything has to be kept
if (hasApi) {
// If there are API pages, preview mode is possible meaning everything has to be kept
if (manifest.hasApiPages) {
return manifest.pages.ssr;
}

Expand Down
1 change: 1 addition & 0 deletions packages/libs/core/src/build/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type BuildOptions = {
domainRedirects: {
[key: string]: string;
};
separateApiLambda?: boolean;
};

export type NextConfig = {
Expand Down
11 changes: 11 additions & 0 deletions packages/libs/core/src/handle/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { toRequest } from "./request";
import { routeDefault } from "../route";
import { addDefaultLocaleToPath, findDomainLocale } from "../route/locale";
import {
ApiRoute,
Event,
ExternalRoute,
PageManifest,
Expand Down Expand Up @@ -52,6 +53,7 @@ export const renderRoute = async (
);
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(renderOpts.pageData));
} else if (route.isApi) {
} else {
await Promise.race([page.render(req, res), event.responsePromise]);
}
Expand Down Expand Up @@ -98,6 +100,7 @@ export const handleDefault = async (
if (route.isRedirect) {
return redirect(event, route as RedirectRoute);
}

if (route.isRender) {
return renderRoute(
event,
Expand All @@ -107,6 +110,14 @@ export const handleDefault = async (
getPage
);
}

if (route.isApi) {
const { page } = route as ApiRoute;
setCustomHeaders(event, routesManifest);
getPage(page).default(event.req, event.res);
return;
}

if (route.isUnauthorized) {
return unauthorized(event, route as UnauthorizedRoute);
}
Expand Down
39 changes: 27 additions & 12 deletions packages/libs/core/src/route/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
PageManifest,
PageRoute,
RoutesManifest,
Request
Request,
ApiRoute
} from "../types";

const pageHtml = (localeUri: string) => {
Expand All @@ -29,7 +30,7 @@ export const handlePageReq = (
routesManifest: RoutesManifest,
isPreview: boolean,
isRewrite?: boolean
): ExternalRoute | PageRoute => {
): ExternalRoute | PageRoute | ApiRoute => {
const { pages } = manifest;
const localeUri = normalise(
addDefaultLocaleToPath(
Expand Down Expand Up @@ -70,11 +71,18 @@ export const handlePageReq = (
return notFoundPage(uri, manifest, routesManifest);
}
if (pages.ssr.nonDynamic[localeUri]) {
return {
isData: false,
isRender: true,
page: pages.ssr.nonDynamic[localeUri]
};
if (localeUri.startsWith("/api/")) {
return {
isApi: true,
page: pages.ssr.nonDynamic[localeUri]
};
} else {
return {
isData: false,
isRender: true,
page: pages.ssr.nonDynamic[localeUri]
};
}
}

const rewrite =
Expand Down Expand Up @@ -116,11 +124,18 @@ export const handlePageReq = (
}
const dynamicSSR = dynamic && pages.ssr.dynamic[dynamic];
if (dynamicSSR) {
return {
isData: false,
isRender: true,
page: dynamicSSR
};
if (dynamic.startsWith("/api/")) {
return {
isApi: true,
page: dynamicSSR
};
} else {
return {
isData: false,
isRender: true,
page: dynamicSSR
};
}
}
const dynamicHTML = dynamic && pages.html.dynamic[dynamic];
if (dynamicHTML) {
Expand Down
2 changes: 2 additions & 0 deletions packages/libs/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export type PageManifest = Manifest & {
[key: string]: string;
};
trailingSlash: boolean;
hasApiPages: boolean;
};

export type HeaderData = {
Expand Down Expand Up @@ -217,4 +218,5 @@ export type Route =
| RedirectRoute
| RenderRoute
| StaticRoute
| ApiRoute
| UnauthorizedRoute;
4 changes: 0 additions & 4 deletions packages/libs/lambda-at-edge/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,6 @@ class Builder {
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, "manifest.json"),
buildManifest
),
fse.writeJson(
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, "api-manifest.json"),
apiBuildManifest
),
fse.copy(
join(this.serverlessDir, "pages"),
join(this.outputDir, DEFAULT_LAMBDA_CODE_DIR, "pages"),
Expand Down
30 changes: 2 additions & 28 deletions packages/libs/lambda-at-edge/src/default-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import Manifest from "./manifest.json";
// @ts-ignore
import RoutesManifestJson from "./routes-manifest.json";
// @ts-ignore
import ApiManifest from "./api-manifest.json";
import lambdaAtEdgeCompat from "@sls-next/next-aws-cloudfront";
import {
ExternalRoute,
getCustomHeaders,
getStaticRegenerationResponse,
getThrottledStaticRegenerationCachePolicy,
handleApi,
handleDefault,
handleFallback,
handlePublicFiles,
Expand All @@ -27,7 +25,6 @@ import {
CloudFrontS3Origin
} from "aws-lambda";
import {
OriginRequestApiHandlerManifest,
OriginRequestDefaultHandlerManifest,
OriginRequestEvent,
OriginResponseEvent,
Expand All @@ -37,10 +34,7 @@ import {
} from "./types";
import { performance } from "perf_hooks";
import type { Readable } from "stream";
import {
createExternalRewriteResponse,
externalRewrite
} from "./routing/rewriter";
import { externalRewrite } from "./routing/rewriter";
import { removeBlacklistedHeaders } from "./headers/removeBlacklistedHeaders";
import { s3BucketNameFromEventRequest } from "./s3/s3BucketNameFromEventRequest";
import { triggerStaticRegeneration } from "./lib/triggerStaticRegeneration";
Expand Down Expand Up @@ -92,7 +86,6 @@ export const handler = async (
event: OriginRequestEvent | OriginResponseEvent
): Promise<CloudFrontResultResponse | CloudFrontRequest> => {
const manifest: OriginRequestDefaultHandlerManifest = Manifest;
const apiManifest: OriginRequestApiHandlerManifest = ApiManifest;
let response: CloudFrontResultResponse | CloudFrontRequest;
const prerenderManifest: PrerenderManifestType = PrerenderManifest;
const routesManifest: RoutesManifest = RoutesManifestJson;
Expand All @@ -112,7 +105,6 @@ export const handler = async (
response = await handleOriginRequest({
event,
manifest,
apiManifest,
prerenderManifest,
routesManifest
});
Expand Down Expand Up @@ -168,13 +160,11 @@ const reconstructOriginalRequestUri = (
const handleOriginRequest = async ({
event,
manifest,
apiManifest,
prerenderManifest,
routesManifest
}: {
event: OriginRequestEvent;
manifest: OriginRequestDefaultHandlerManifest;
apiManifest: OriginRequestApiHandlerManifest;
prerenderManifest: PrerenderManifestType;
routesManifest: RoutesManifest;
}) => {
Expand All @@ -186,23 +176,6 @@ const handleOriginRequest = async ({
}
);

// TOOO: handle cross rewrites from API <-> Pages
if (request.uri.startsWith(`${routesManifest.basePath}/api`)) {
const externalApi = await handleApi(
{ req, res, responsePromise },
apiManifest,
routesManifest,
(pagePath: string) => require(`./${pagePath}`)
);

if (externalApi) {
const { path } = externalApi;
await createExternalRewriteResponse(path, req, res, request.body?.data);
}

return await responsePromise;
}

const { now, log } = perfLogger(manifest.logLambdaExecutionTimes);

let tBeforeSSR = null;
Expand Down Expand Up @@ -243,6 +216,7 @@ const handleOriginRequest = async ({
const relativeFile = isData ? file : file.slice("pages".length);
return staticRequest(request, relativeFile, path);
}

const external: ExternalRoute = route;
const { path } = external;
return externalRewrite(event, manifest.enableHTTPCompression, path);
Expand Down

0 comments on commit f058d51

Please sign in to comment.