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

Commit b4666d3

Browse files
authored
feat(lambda-at-edge): initial support for routing to locale-specific … (#884)
1 parent 8bc889c commit b4666d3

File tree

15 files changed

+386
-118
lines changed

15 files changed

+386
-118
lines changed

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

Lines changed: 186 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,144 @@ 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+
645+
// Initial default value
646+
localeSsrPages.dynamic[newKey] = { file: "", regex: "" };
647+
const newDynamicSsr = Object.assign(
648+
localeSsrPages.dynamic[newKey],
649+
ssrPages.dynamic[key]
650+
);
651+
652+
// Need to update the regex
653+
newDynamicSsr.regex = pathToRegexStr(newKey);
654+
}
655+
656+
ssrPages.dynamic = {
657+
...ssrPages.dynamic,
658+
...localeSsrPages.dynamic
659+
};
660+
661+
const localeSsgPages: {
662+
dynamic: {
663+
[key: string]: DynamicSsgRoute;
664+
};
665+
nonDynamic: {
666+
[key: string]: SsgRoute;
667+
};
668+
} = {
669+
dynamic: {},
670+
nonDynamic: {}
671+
};
672+
673+
for (const key in ssgPages.nonDynamic) {
674+
const newKey = key === "/" ? `/${locale}` : `/${locale}${key}`;
675+
676+
// Initial default value
677+
localeSsgPages.nonDynamic[newKey] = {
678+
initialRevalidateSeconds: false,
679+
srcRoute: null,
680+
dataRoute: ""
681+
};
682+
683+
const newSsgRoute = Object.assign(
684+
localeSsgPages.nonDynamic[newKey],
685+
ssgPages.nonDynamic[key]
686+
);
687+
688+
// Replace with localized value
689+
newSsgRoute.dataRoute = newSsgRoute.dataRoute.replace(
690+
`/_next/data/${buildId}/`,
691+
`/_next/data/${buildId}/${locale}/`
692+
);
693+
}
694+
695+
ssgPages.nonDynamic = {
696+
...ssgPages.nonDynamic,
697+
...localeSsgPages.nonDynamic
698+
};
699+
700+
for (const key in ssgPages.dynamic) {
701+
const newKey = key === "/" ? `/${locale}` : `/${locale}${key}`;
702+
localeSsgPages.dynamic[newKey] = ssgPages.dynamic[key];
703+
704+
const newDynamicSsgRoute = localeSsgPages.dynamic[newKey];
705+
706+
// Replace with localized values
707+
newDynamicSsgRoute.dataRoute = newDynamicSsgRoute.dataRoute.replace(
708+
`/_next/data/${buildId}/`,
709+
`/_next/data/${buildId}/${locale}/`
710+
);
711+
newDynamicSsgRoute.dataRouteRegex = newDynamicSsgRoute.dataRouteRegex.replace(
712+
`/_next/data/${buildId}/`,
713+
`/_next/data/${buildId}/${locale}/`
714+
);
715+
newDynamicSsgRoute.fallback =
716+
typeof newDynamicSsgRoute.fallback === "string"
717+
? newDynamicSsgRoute.fallback.replace("/", `/${locale}/`)
718+
: newDynamicSsgRoute.fallback;
719+
newDynamicSsgRoute.routeRegex = localeSsgPages.dynamic[
720+
newKey
721+
].routeRegex.replace("^/", `^/${locale}/`);
722+
}
723+
724+
ssgPages.dynamic = {
725+
...ssgPages.dynamic,
726+
...localeSsgPages.dynamic
727+
};
728+
}
729+
}
730+
}
731+
585732
const publicFiles = await this.readPublicFiles();
586733

587734
publicFiles.forEach((pf) => {
@@ -727,26 +874,16 @@ class Builder {
727874
);
728875
const destination = path.join(
729876
assetOutputDirectory,
730-
withBasePath(`_next/data/${buildId}/${localePrefixedJSONFileName}`)
877+
withBasePath(
878+
`_next/data/${buildId}/${
879+
defaultLocale && defaultLocale === locale
880+
? JSONFileName
881+
: localePrefixedJSONFileName
882+
}`
883+
)
731884
);
732885

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-
}
886+
return copyIfExists(source, destination);
750887
})
751888
);
752889

@@ -765,32 +902,22 @@ class Builder {
765902
const destination = path.join(
766903
assetOutputDirectory,
767904
withBasePath(
768-
path.join("static-pages", buildId, localePrefixedPageFilePath)
905+
path.join(
906+
"static-pages",
907+
buildId,
908+
defaultLocale && defaultLocale === locale
909+
? pageFilePath
910+
: localePrefixedPageFilePath
911+
)
769912
)
770913
);
771914

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-
}
915+
return copyIfExists(source, destination);
789916
})
790917
);
791918

792919
fallbackHTMLPageAssets.concat(
793-
Object.values(prerenderManifest.dynamicRoutes || {})
920+
Object.values(prerenderManifest.dynamicRoutes ?? {})
794921
.filter(({ fallback }) => {
795922
return !!fallback;
796923
})
@@ -806,27 +933,17 @@ class Builder {
806933
const destination = path.join(
807934
assetOutputDirectory,
808935
withBasePath(
809-
path.join("static-pages", buildId, localePrefixedFallback)
936+
path.join(
937+
"static-pages",
938+
buildId,
939+
defaultLocale && defaultLocale === locale
940+
? fallback
941+
: localePrefixedFallback
942+
)
810943
)
811944
);
812945

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-
}
946+
return copyIfExists(source, destination);
830947
})
831948
);
832949
}
@@ -927,11 +1044,21 @@ class Builder {
9271044
await restoreUserConfig();
9281045
}
9291046

1047+
const routesManifest = require(join(
1048+
this.dotNextDir,
1049+
"routes-manifest.json"
1050+
));
1051+
1052+
const prerenderManifest = require(join(
1053+
this.dotNextDir,
1054+
"prerender-manifest.json"
1055+
));
1056+
9301057
const {
9311058
defaultBuildManifest,
9321059
apiBuildManifest,
9331060
imageBuildManifest
934-
} = await this.prepareBuildManifests();
1061+
} = await this.prepareBuildManifests(routesManifest, prerenderManifest);
9351062

9361063
await this.buildDefaultLambda(defaultBuildManifest);
9371064

@@ -953,10 +1080,6 @@ class Builder {
9531080
}
9541081

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

0 commit comments

Comments
 (0)