Skip to content

Commit 0fa32ed

Browse files
committed
add stylesheet to SSR preloading to prevent FOUC
1 parent 1e6d157 commit 0fa32ed

1 file changed

Lines changed: 33 additions & 13 deletions

File tree

packages/start/src/server/StartServer.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,43 +31,63 @@ function matchRoute(matches: any[], routes: any[], matched = []): any[] | undefi
3131
}
3232
}
3333

34+
/**
35+
*
36+
*@info flattens, deduplicate by item.attrs.key and returns array of unique items respecting order of initial param.
37+
*/
38+
function dedupeAssets(assets: Asset[][]): Asset[] {
39+
return [...new Map(assets.flat().map(item => [item.attrs.key, item])).values()];
40+
}
41+
3442
/**
3543
*
3644
* Read more: https://docs.solidjs.com/solid-start/reference/server/start-server
3745
*/
3846
export function StartServer(props: { document: Component<DocumentComponentProps> }) {
39-
const context = getRequestEvent() as PageEvent;
40-
// @ts-ignore
47+
const context = getRequestEvent() as PageEvent & { nonce?: string };
48+
4149
const nonce = context.nonce;
4250

4351
let assets: Asset[] = [];
4452
Promise.resolve().then(async () => {
4553
let assetPromises: Promise<Asset[]>[] = [];
46-
// @ts-ignore
54+
// @ts-expect-error - context.router is not typed
4755
if (context.router && context.router.matches) {
48-
// @ts-ignore
56+
// @ts-expect-error - context.router is not typed
4957
const matches = [...context.router.matches];
5058
while (matches.length && (!matches[0].info || !matches[0].info.filesystem)) matches.shift();
5159
const matched = matches.length && matchRoute(matches, context.routes);
5260
if (matched) {
5361
const inputs = import.meta.env.MANIFEST[import.meta.env.START_ISLANDS ? "ssr" : "client"]!
54-
.inputs
62+
.inputs;
5563
for (let i = 0; i < matched.length; i++) {
5664
const segment = matched[i];
5765
const part = inputs[segment["$component"].src]!;
5866
assetPromises.push(part.assets() as any);
5967
}
6068
} else if (import.meta.env.DEV) console.warn("No route matched for preloading js assets");
6169
}
62-
assets = await Promise.all(assetPromises).then(a =>
63-
// dedupe assets
64-
[...new Map(a.flat().map(item => [item.attrs.key, item])).values()].filter(asset =>
65-
import.meta.env.START_ISLANDS
66-
? false
67-
: (asset.attrs as JSX.LinkHTMLAttributes<HTMLLinkElement>).rel === "modulepreload" &&
70+
assets = await Promise.all(assetPromises).then(assetList => {
71+
if (import.meta.env.START_ISLANDS) {
72+
return [];
73+
} else {
74+
return dedupeAssets(assetList).filter(asset => {
75+
const assetAttrs = asset.attrs as JSX.LinkHTMLAttributes<HTMLLinkElement>;
76+
77+
/**
78+
* @info besides `modulepreload` we also want `stylesheet` to be preloaded during SSR.
79+
* this will prevent FOUC on the client for non-global CSS.
80+
*/
81+
const isPreloadOrStylesheet =
82+
assetAttrs.rel === "modulepreload" || assetAttrs.rel === "stylesheet";
83+
84+
return (
85+
isPreloadOrStylesheet &&
6886
!context.assets.find((a: Asset) => a.attrs.key === asset.attrs.key)
69-
)
70-
);
87+
);
88+
});
89+
}
90+
});
7191
});
7292

7393
useAssets(() => (assets.length ? assets.map(m => renderAsset(m)) : undefined));

0 commit comments

Comments
 (0)