@nimpl/router
Version:
Edge router for next.js apps (i18n, basePath, rewrites, redirects)
177 lines (176 loc) • 7.83 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Router = void 0;
const server_1 = require("next/server");
const headers_1 = require("next/headers");
const format_destination_1 = require("./utils/format-destination");
const test_rule_1 = require("./utils/test-rule");
class Router {
rewrites;
redirects;
basePath;
i18n;
constructor(opts) {
this.redirects = opts.redirects;
this.rewrites = opts.rewrites;
this.basePath = opts.basePath?.replace(/\/$/, "");
this.i18n = opts.i18n;
}
getRequestLocale = async (url) => {
if (!this.i18n)
return null;
const domainI18n = this.i18n.domains?.find((domainData) => domainData.domain === url.host);
const locales = domainI18n?.locales || this.i18n.locales;
const defaultLocale = domainI18n?.defaultLocale || this.i18n.defaultLocale;
const pathnameLocale = url.pathname.match(new RegExp(`/(?<locale>${locales.join("|")})(?:/|$)`));
let preferredLocale = pathnameLocale?.groups?.locale;
if (!preferredLocale) {
const cookiesStore = await (0, headers_1.cookies)();
preferredLocale = cookiesStore.get("NEXT_LOCALE")?.value;
}
if (preferredLocale)
return preferredLocale;
const detectedLocale = this.i18n.localeDetector?.(url);
return detectedLocale || defaultLocale;
};
findChanger = async (changers, url, routeData) => {
for (const changer of changers) {
let targetLocale = null;
let targetBasePath = null;
if (this.basePath && changer.basePath !== false) {
if (routeData.basePath) {
targetBasePath = this.basePath;
}
else {
continue;
}
}
if (changer.locale !== false && routeData.locale) {
targetLocale = routeData.locale;
}
const match = url.pathname.match(`^${targetBasePath || ""}${targetLocale ? `/${routeData.locale}` : ""}${changer.source.replace(/([^/])$/, "$1/")}?$`);
if (!match)
continue;
const groups = match.groups || {};
const testRule = (0, test_rule_1.createRuleTester)();
if (changer.has) {
let validRules = true;
for (const rule of changer.has) {
const { match, groups: ruleGroups } = await testRule(url, rule);
if (!match) {
validRules = false;
break;
}
Object.assign(groups, ruleGroups);
}
if (!validRules)
continue;
}
if (changer.missing) {
let validRules = true;
for (const rule of changer.missing) {
const { match, groups: ruleGroups } = await testRule(url, rule);
if (match) {
validRules = false;
break;
}
Object.assign(groups, ruleGroups);
}
if (!validRules)
continue;
}
return {
changer: changer,
groups,
};
}
};
parsePathname(pathname) {
const routeData = {
basePath: null,
locale: null,
pathname,
};
const basePathMatch = this.basePath && pathname.match(new RegExp(`^${this.basePath}(/|$)`));
if (basePathMatch) {
routeData.basePath = this.basePath;
routeData.pathname = routeData.pathname.replace(new RegExp(`^${this.basePath}(/|$)`), "/");
}
const localeMatch = this.i18n && routeData.pathname.match(new RegExp(`^/(${this.i18n.locales.join("|")})(/|$)`));
if (localeMatch) {
routeData.locale = localeMatch[1];
routeData.pathname = routeData.pathname.replace(new RegExp(`^/${routeData.locale}(/|$)`), "/");
}
return routeData;
}
async navigate(nextUrl) {
const originRouteData = this.parsePathname(nextUrl.pathname);
const nextRouteData = {
basePath: this.basePath,
locale: originRouteData.locale,
pathname: originRouteData.pathname,
type: "none",
status: 200,
};
if (!nextRouteData.locale)
nextRouteData.locale = await this.getRequestLocale(nextUrl);
if (this.redirects) {
const targetChanger = await this.findChanger(this.redirects, nextUrl, originRouteData);
if (targetChanger) {
const destination = (0, format_destination_1.formatDestination)(targetChanger.changer.destination, targetChanger.groups);
nextRouteData.pathname = destination;
if (targetChanger.changer.locale === false)
nextRouteData.locale = null;
if (targetChanger.changer.basePath === false)
nextRouteData.basePath = null;
nextRouteData.type = "redirect";
nextRouteData.status = targetChanger.changer.permanent ? 308 : 307;
}
}
if (this.rewrites && nextRouteData.type !== "redirect") {
const targetChanger = await this.findChanger(this.rewrites, nextUrl, originRouteData);
if (targetChanger) {
const destination = (0, format_destination_1.formatDestination)(targetChanger.changer.destination, targetChanger.groups);
nextRouteData.pathname = destination;
if (targetChanger.changer.locale === false)
nextRouteData.locale = null;
if (targetChanger.changer.basePath === false)
nextRouteData.basePath = null;
nextRouteData.type = "rewrite";
}
}
if (this.basePath && nextRouteData.basePath && nextRouteData.type === "none") {
if (originRouteData.basePath) {
nextRouteData.type = "rewrite";
}
else {
const newNextUrl = new URL(nextUrl);
newNextUrl.pathname = "/404/";
return server_1.NextResponse.rewrite(newNextUrl, { status: 404 });
}
}
// if i18n configurated and locale wasnt disabled by rewrite or redirect
if (this.i18n && nextRouteData.locale && nextRouteData.type !== "redirect") {
// if in original route was default locale - redirect to pathname without default locale
if (originRouteData.locale === this.i18n.defaultLocale) {
nextRouteData.type = "redirect";
nextRouteData.status = 307;
}
else {
nextRouteData.type = "rewrite";
}
}
if (nextRouteData.type === "redirect") {
const newNextUrl = new URL(nextUrl);
newNextUrl.pathname = `${nextRouteData.basePath || ""}${!this.i18n || !nextRouteData.locale || nextRouteData.locale === this.i18n.defaultLocale ? "" : `/${nextRouteData.locale}`}${nextRouteData.pathname}`;
return server_1.NextResponse.redirect(newNextUrl, { status: nextRouteData.status });
}
if (nextRouteData.type === "rewrite") {
const newNextUrl = new URL(nextUrl);
newNextUrl.pathname = `${nextRouteData.locale ? `/${nextRouteData.locale}` : ""}${nextRouteData.pathname}`;
return server_1.NextResponse.rewrite(newNextUrl, { status: nextRouteData.status });
}
return server_1.NextResponse.next();
}
}
exports.Router = Router;