Skip to content
This repository was archived by the owner on Jan 28, 2025. It is now read-only.

Commit 4344d2d

Browse files
committed
feat(lambda-at-edge): initial support for routing to locale-specific pages
1 parent 8bc889c commit 4344d2d

File tree

15 files changed

+377
-117
lines changed

15 files changed

+377
-117
lines changed

packages/libs/lambda-at-edge/src/build.ts

Lines changed: 178 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
OriginRequestDefaultHandlerManifest,
1010
OriginRequestApiHandlerManifest,
1111
RoutesManifest,
12-
OriginRequestImageHandlerManifest
12+
OriginRequestImageHandlerManifest,
13+
DynamicPageKeyValue
1314
} from "./types";
1415
import { isDynamicRoute, isOptionalCatchAllRoute } from "./lib/isDynamicRoute";
1516
import pathToPosix from "./lib/pathToPosix";
@@ -23,7 +24,7 @@ import createServerlessConfig from "./lib/createServerlessConfig";
2324
import { isTrailingSlashRedirect } from "./routing/redirector";
2425
import readDirectoryFiles from "./lib/readDirectoryFiles";
2526
import filterOutDirectories from "./lib/filterOutDirectories";
26-
import { PrerenderManifest } from "next/dist/build";
27+
import { DynamicSsgRoute, PrerenderManifest, SsgRoute } from "next/dist/build";
2728
import { Item } from "klaw";
2829
import { Job } from "@vercel/nft/out/node-file-trace";
2930

@@ -456,7 +457,10 @@ class Builder {
456457
]);
457458
}
458459

459-
async prepareBuildManifests(): Promise<{
460+
async prepareBuildManifests(
461+
routesManifest: RoutesManifest,
462+
prerenderManifest: PrerenderManifest
463+
): Promise<{
460464
defaultBuildManifest: OriginRequestDefaultHandlerManifest;
461465
apiBuildManifest: OriginRequestApiHandlerManifest;
462466
imageBuildManifest: OriginRequestImageHandlerManifest;
@@ -487,6 +491,10 @@ class Builder {
487491
html: {
488492
dynamic: {},
489493
nonDynamic: {}
494+
},
495+
ssg: {
496+
dynamic: {},
497+
nonDynamic: {}
490498
}
491499
},
492500
publicFiles: {},
@@ -506,6 +514,7 @@ class Builder {
506514
enableHTTPCompression
507515
};
508516

517+
const ssgPages = defaultBuildManifest.pages.ssg;
509518
const ssrPages = defaultBuildManifest.pages.ssr;
510519
const htmlPages = defaultBuildManifest.pages.html;
511520
const apiPages = apiBuildManifest.apis;
@@ -582,6 +591,136 @@ class Builder {
582591
}
583592
});
584593

594+
// Add non-dynamic SSG routes
595+
Object.entries(prerenderManifest.routes).forEach(([route, ssgRoute]) => {
596+
// Somehow Next.js generates prerender manifest with default locale prefixed, normalize it
597+
const defaultLocale = routesManifest.i18n?.defaultLocale;
598+
if (defaultLocale) {
599+
const normalizedRoute = route.replace(`/${defaultLocale}/`, "/");
600+
ssgRoute.dataRoute = ssgRoute.dataRoute.replace(
601+
`/${defaultLocale}/`,
602+
"/"
603+
);
604+
ssgPages.nonDynamic[normalizedRoute] = ssgRoute;
605+
} else {
606+
ssgPages.nonDynamic[route] = ssgRoute;
607+
}
608+
});
609+
610+
// Add dynamic SSG routes
611+
Object.entries(prerenderManifest.dynamicRoutes ?? {}).forEach(
612+
([route, dynamicSsgRoute]) => {
613+
ssgPages.dynamic[route] = dynamicSsgRoute;
614+
}
615+
);
616+
617+
// Copy routes for all specified locales
618+
if (routesManifest.i18n) {
619+
const defaultLocale = routesManifest.i18n.defaultLocale;
620+
for (const locale of routesManifest.i18n.locales) {
621+
if (locale !== defaultLocale) {
622+
const localeSsrPages: {
623+
nonDynamic: {
624+
[key: string]: string;
625+
};
626+
dynamic: DynamicPageKeyValue;
627+
} = {
628+
nonDynamic: {},
629+
dynamic: {}
630+
};
631+
632+
for (const key in ssrPages.nonDynamic) {
633+
const newKey = key === "/" ? `/${locale}` : `/${locale}${key}`;
634+
localeSsrPages.nonDynamic[newKey] = ssrPages.nonDynamic[key];
635+
}
636+
637+
ssrPages.nonDynamic = {
638+
...ssrPages.nonDynamic,
639+
...localeSsrPages.nonDynamic
640+
};
641+
642+
for (const key in ssrPages.dynamic) {
643+
const newKey = key === "/" ? `/${locale}` : `/${locale}${key}`;
644+
localeSsrPages.dynamic[newKey] = {};
645+
const newDynamicSsr = Object.assign(
646+
localeSsrPages.dynamic[newKey],
647+
ssrPages.dynamic[key]
648+
);
649+
650+
// Need to update the regex
651+
newDynamicSsr.regex = pathToRegexStr(newKey);
652+
}
653+
654+
ssrPages.dynamic = {
655+
...ssrPages.dynamic,
656+
...localeSsrPages.dynamic
657+
};
658+
659+
const localeSsgPages: {
660+
dynamic: {
661+
[key: string]: DynamicSsgRoute;
662+
};
663+
nonDynamic: {
664+
[key: string]: SsgRoute;
665+
};
666+
} = {
667+
dynamic: {},
668+
nonDynamic: {}
669+
};
670+
671+
for (const key in ssgPages.nonDynamic) {
672+
const newKey = key === "/" ? `/${locale}` : `/${locale}${key}`;
673+
localeSsgPages.nonDynamic[newKey] = {};
674+
675+
const newSsgRoute = Object.assign(
676+
localeSsgPages.nonDynamic[newKey],
677+
ssgPages.nonDynamic[key]
678+
);
679+
680+
// Replace with localized value
681+
newSsgRoute.dataRoute = newSsgRoute.dataRoute.replace(
682+
`/_next/data/${buildId}/`,
683+
`/_next/data/${buildId}/${locale}/`
684+
);
685+
}
686+
687+
ssgPages.nonDynamic = {
688+
...ssgPages.nonDynamic,
689+
...localeSsgPages.nonDynamic
690+
};
691+
692+
for (const key in ssgPages.dynamic) {
693+
const newKey = key === "/" ? `/${locale}` : `/${locale}${key}`;
694+
localeSsgPages.dynamic[newKey] = ssgPages.dynamic[key];
695+
696+
const newDynamicSsgRoute = localeSsgPages.dynamic[newKey];
697+
698+
// Replace with localized values
699+
newDynamicSsgRoute.dataRoute = newDynamicSsgRoute.dataRoute.replace(
700+
`/_next/data/${buildId}/`,
701+
`/_next/data/${buildId}/${locale}/`
702+
);
703+
newDynamicSsgRoute.dataRouteRegex = newDynamicSsgRoute.dataRouteRegex.replace(
704+
`/_next/data/${buildId}/`,
705+
`/_next/data/${buildId}/${locale}/`
706+
);
707+
newDynamicSsgRoute.fallback =
708+
typeof newDynamicSsgRoute.fallback === "string"
709+
? newDynamicSsgRoute.fallback.replace("/", `/${locale}/`)
710+
: newDynamicSsgRoute.fallback;
711+
newDynamicSsgRoute.routeRegex = localeSsgPages.dynamic[
712+
newKey
713+
].routeRegex.replace("^/", `^/${locale}/`);
714+
}
715+
716+
ssgPages.dynamic = {
717+
...ssgPages.dynamic,
718+
...localeSsgPages.dynamic
719+
};
720+
}
721+
}
722+
}
723+
585724
const publicFiles = await this.readPublicFiles();
586725

587726
publicFiles.forEach((pf) => {
@@ -727,26 +866,16 @@ class Builder {
727866
);
728867
const destination = path.join(
729868
assetOutputDirectory,
730-
withBasePath(`_next/data/${buildId}/${localePrefixedJSONFileName}`)
869+
withBasePath(
870+
`_next/data/${buildId}/${
871+
defaultLocale && defaultLocale === locale
872+
? JSONFileName
873+
: localePrefixedJSONFileName
874+
}`
875+
)
731876
);
732877

733-
if (defaultLocale && defaultLocale === locale) {
734-
// If this is default locale, we need to copy to two destinations
735-
// the locale-prefixed path and non-locale-prefixed path
736-
const defaultDestination = path.join(
737-
assetOutputDirectory,
738-
withBasePath(`_next/data/${buildId}/${JSONFileName}`)
739-
);
740-
741-
return new Promise(async () => {
742-
await Promise.all([
743-
copyIfExists(source, destination),
744-
copyIfExists(source, defaultDestination)
745-
]);
746-
});
747-
} else {
748-
return copyIfExists(source, destination);
749-
}
878+
return copyIfExists(source, destination);
750879
})
751880
);
752881

@@ -765,32 +894,22 @@ class Builder {
765894
const destination = path.join(
766895
assetOutputDirectory,
767896
withBasePath(
768-
path.join("static-pages", buildId, localePrefixedPageFilePath)
897+
path.join(
898+
"static-pages",
899+
buildId,
900+
defaultLocale && defaultLocale === locale
901+
? pageFilePath
902+
: localePrefixedPageFilePath
903+
)
769904
)
770905
);
771906

772-
if (defaultLocale && defaultLocale === locale) {
773-
// If this is default locale, we need to copy to two destinations
774-
// the locale-prefixed path and non-locale-prefixed path
775-
const defaultDestination = path.join(
776-
assetOutputDirectory,
777-
withBasePath(path.join("static-pages", buildId, pageFilePath))
778-
);
779-
780-
return new Promise(async () => {
781-
await Promise.all([
782-
copyIfExists(source, destination),
783-
copyIfExists(source, defaultDestination)
784-
]);
785-
});
786-
} else {
787-
return copyIfExists(source, destination);
788-
}
907+
return copyIfExists(source, destination);
789908
})
790909
);
791910

792911
fallbackHTMLPageAssets.concat(
793-
Object.values(prerenderManifest.dynamicRoutes || {})
912+
Object.values(prerenderManifest.dynamicRoutes ?? {})
794913
.filter(({ fallback }) => {
795914
return !!fallback;
796915
})
@@ -806,27 +925,17 @@ class Builder {
806925
const destination = path.join(
807926
assetOutputDirectory,
808927
withBasePath(
809-
path.join("static-pages", buildId, localePrefixedFallback)
928+
path.join(
929+
"static-pages",
930+
buildId,
931+
defaultLocale && defaultLocale === locale
932+
? fallback
933+
: localePrefixedFallback
934+
)
810935
)
811936
);
812937

813-
if (defaultLocale && defaultLocale === locale) {
814-
// If this is default locale, we need to copy to two destinations
815-
// the locale-prefixed path and non-locale-prefixed path
816-
const defaultDestination = path.join(
817-
assetOutputDirectory,
818-
withBasePath(path.join("static-pages", buildId, fallback))
819-
);
820-
821-
return new Promise(async () => {
822-
await Promise.all([
823-
copyIfExists(source, destination),
824-
copyIfExists(source, defaultDestination)
825-
]);
826-
});
827-
} else {
828-
return copyIfExists(source, destination);
829-
}
938+
return copyIfExists(source, destination);
830939
})
831940
);
832941
}
@@ -927,11 +1036,21 @@ class Builder {
9271036
await restoreUserConfig();
9281037
}
9291038

1039+
const routesManifest = require(join(
1040+
this.dotNextDir,
1041+
"routes-manifest.json"
1042+
));
1043+
1044+
const prerenderManifest = require(join(
1045+
this.dotNextDir,
1046+
"prerender-manifest.json"
1047+
));
1048+
9301049
const {
9311050
defaultBuildManifest,
9321051
apiBuildManifest,
9331052
imageBuildManifest
934-
} = await this.prepareBuildManifests();
1053+
} = await this.prepareBuildManifests(routesManifest, prerenderManifest);
9351054

9361055
await this.buildDefaultLambda(defaultBuildManifest);
9371056

@@ -953,10 +1072,6 @@ class Builder {
9531072
}
9541073

9551074
// Copy static assets to .serverless_nextjs directory
956-
const routesManifest = require(join(
957-
this.dotNextDir,
958-
"routes-manifest.json"
959-
));
9601075
await this.buildStaticAssets(defaultBuildManifest, routesManifest);
9611076
}
9621077

0 commit comments

Comments
 (0)