@fleek-platform/next-on-fleek
Version:
`@fleek-platform/next-on-fleek` is a CLI tool that you can use to build and develop [Next.js](https://nextjs.org/) applications so that they can run on [Fleek Functions](https://fleek.xyz/docs/platform/fleek-functions/).
131 lines (118 loc) • 3.85 kB
text/typescript
import type { MatchedSet } from './utils';
import {
applyHeaders,
applySearchParams,
isUrl,
runOrFetchBuildOutputItem,
} from './utils';
import { RoutesMatcher } from './routes-matcher';
import type { RequestContext } from '../../src/utils/requestContext';
/**
* Handles a request by processing and matching it against all the routing phases.
*
* @param reqCtx Request Context object (contains all we need in to know regarding the request in order to handle it).
* @param config The processed Vercel build output config.
* @param output Vercel build output.
* @param buildMetadata Metadata generated by the next-on-pages build process.
* @returns An instance of the router.
*/
export async function handleRequest(
reqCtx: RequestContext,
config: ProcessedVercelConfig,
output: VercelBuildOutput,
buildMetadata: NextOnPagesBuildMetadata,
): Promise<Response> {
const matcher = new RoutesMatcher(
config.routes,
output,
reqCtx,
buildMetadata,
config.wildcard,
);
const match = await findMatch(matcher);
return generateResponse(reqCtx, match, output);
}
/**
* Finds a match for the request.
*
* @param matcher Instance of the matcher for the request.
* @param phase The phase to run, either `none` or `error`.
* @param skipErrorMatch Whether to skip the error match.
* @returns The matched set of path, status, headers, and search params.
*/
async function findMatch(
matcher: RoutesMatcher,
phase: 'none' | 'error' = 'none',
skipErrorMatch = false,
): Promise<MatchedSet> {
const result = await matcher.run(phase);
if (
result === 'error' ||
(!skipErrorMatch && matcher.status && matcher.status >= 400)
) {
return findMatch(matcher, 'error', true);
}
return {
path: matcher.path,
status: matcher.status,
headers: matcher.headers,
searchParams: matcher.searchParams,
body: matcher.body,
};
}
/**
* Serves a file from the Vercel build output.
*
* @param reqCtx Request Context object.
* @param match The match from the Vercel build output.
* @returns A response object.
*/
async function generateResponse(
reqCtx: RequestContext,
{ path = '/404', status, headers, searchParams, body }: MatchedSet,
output: VercelBuildOutput,
): Promise<Response> {
// Redirect user to external URL for redirects.
const locationHeader = headers.normal.get('location');
if (locationHeader) {
// Apply the search params to the location header if it was not from middleware.
// Middleware that returns a redirect will specify the destination, including any search params
// that they want to include. Therefore, we should not be appending search params to those.
if (locationHeader !== headers.middlewareLocation) {
const paramsStr = [...searchParams.keys()].length
? `?${searchParams.toString()}`
: '';
headers.normal.set('location', `${locationHeader ?? '/'}${paramsStr}`);
}
return new Response(null, { status, headers: headers.normal });
}
let resp: Response;
if (body !== undefined) {
// If we have a response body from matching, use it instead.
resp = new Response(body, { status });
} else if (isUrl(path)) {
// If the path is an URL from matching, that means it was rewritten to a full URL.
const url = new URL(path);
applySearchParams(url.searchParams, searchParams);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
resp = await fetch(url, reqCtx.request);
} else {
// Otherwise, we need to serve a file from the Vercel build output.
resp = await runOrFetchBuildOutputItem(output[path], reqCtx, {
path,
status,
headers,
searchParams,
});
}
const newHeaders = headers.normal;
applyHeaders(newHeaders, resp.headers);
applyHeaders(newHeaders, headers.important);
resp = new Response(resp.body, {
...resp,
status: status || resp.status,
headers: newHeaders,
});
return resp;
}