@sls-next/core
Version:
Handles Next.js routing independent of provider
216 lines (191 loc) • 5.35 kB
text/typescript
import { STATUS_CODES } from "http";
import {
addDefaultLocaleToPath,
getAcceptLanguageLocale,
getLocaleDomainRedirect
} from "./locale";
import { compileDestination, matchPath } from "../match";
import { Manifest, Request, RedirectRoute, RoutesManifest } from "../types";
import { parse } from "cookie";
/**
* Create a redirect response with the given status code
* @param uri
* @param querystring
* @param statusCode
*/
export function createRedirectResponse(
uri: string,
querystring: string | undefined,
statusCode: number
): RedirectRoute {
let location;
// Properly join query strings
if (querystring) {
const [uriPath, uriQuery] = uri.split("?");
location = `${uriPath}?${querystring}${uriQuery ? `&${uriQuery}` : ""}`;
} else {
location = uri;
}
const status = statusCode;
const statusDescription = STATUS_CODES[status];
const refresh =
statusCode === 308
? [
// Required for IE11 compatibility
{
key: "Refresh",
value: `0;url=${location}`
}
]
: [];
const cacheControl = [
{
key: "Cache-Control",
value: "s-maxage=0"
}
];
return {
isRedirect: true,
status: status,
statusDescription: statusDescription || "",
headers: {
location: [
{
key: "Location",
value: location
}
],
refresh: refresh,
"cache-control": cacheControl
}
};
}
/**
* Get a domain redirect such as redirecting www to non-www domain.
* @param request
* @param manifest
*/
export function getDomainRedirectPath(
request: Request,
manifest: Manifest
): string | undefined {
const hostHeaders = request.headers["host"];
if (hostHeaders && hostHeaders.length > 0) {
const host = hostHeaders[0].value;
const domainRedirects = manifest.domainRedirects;
if (domainRedirects && domainRedirects[host]) {
return `${domainRedirects[host]}${request.uri}`;
}
}
}
/**
* Redirect from root to locale.
* @param req
* @param routesManifest
* @param manifest
*/
export async function getLanguageRedirectPath(
req: Request,
manifest: Manifest,
routesManifest: RoutesManifest
): Promise<string | undefined> {
// Check for disabled locale detection: https://nextjs.org/docs/advanced-features/i18n-routing#disabling-automatic-locale-detection
if (routesManifest.i18n?.localeDetection === false) {
return undefined;
}
// Try to get locale domain redirect
const localeDomainRedirect = await getLocaleDomainRedirect(
req,
routesManifest
);
if (localeDomainRedirect) {
return localeDomainRedirect;
}
const basePath = routesManifest.basePath;
const trailingSlash = manifest.trailingSlash;
const rootUri = basePath ? `${basePath}${trailingSlash ? "/" : ""}` : "/";
// NEXT_LOCALE in cookie will override any accept-language header
// per: https://nextjs.org/docs/advanced-features/i18n-routing#leveraging-the-next_locale-cookie
const headerCookies = req.headers.cookie
? req.headers.cookie[0]?.value
: undefined;
if (req.uri === rootUri && headerCookies) {
const cookies = parse(headerCookies);
const nextLocale = cookies["NEXT_LOCALE"];
if (nextLocale) {
return await getAcceptLanguageLocale(
nextLocale,
manifest,
routesManifest
);
}
}
const languageHeader = req.headers["accept-language"];
const acceptLanguage = languageHeader && languageHeader[0]?.value;
if (req.uri === rootUri && acceptLanguage) {
return await getAcceptLanguageLocale(
acceptLanguage,
manifest,
routesManifest
);
}
}
/**
* Get the redirect of the given path, if it exists.
* @param request
* @param routesManifest
*/
export function getRedirectPath(
request: Request,
routesManifest: RoutesManifest
): { path: string; statusCode: number } | null {
const path = addDefaultLocaleToPath(request.uri, routesManifest);
const redirects = routesManifest.redirects ?? [];
for (const redirect of redirects) {
const match = matchPath(path, redirect.source);
if (match) {
const compiledDestination = compileDestination(
redirect.destination,
match.params
);
if (!compiledDestination) {
return null;
}
return {
path: compiledDestination,
statusCode: redirect.statusCode
};
}
}
return null;
}
/**
* Get a domain redirect such as redirecting www to non-www domain.
* @param request
* @param manifest
*/
export function getTrailingSlashPath(
request: Request,
manifest: Manifest,
isFile: boolean
): string | undefined {
const { uri } = request;
if (isFile) {
// Data requests and public files with trailing slash URL always get
// redirected to non-trailing slash URL
if (uri.endsWith("/")) {
return uri.slice(0, -1);
}
} else if (/^\/[^/]/.test(request.uri)) {
// HTML/SSR pages get redirected based on trailingSlash in next.config.js
// We do not redirect:
// Unnormalised URI is "/" or "" as this could cause a redirect loop
const trailingSlash = manifest.trailingSlash;
if (!trailingSlash && uri.endsWith("/")) {
return uri.slice(0, -1);
}
if (trailingSlash && !uri.endsWith("/")) {
return uri + "/";
}
}
}