-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy pathgatsby-ssr.tsx
More file actions
137 lines (117 loc) · 4.67 KB
/
Copy pathgatsby-ssr.tsx
File metadata and controls
137 lines (117 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import React from 'react';
import type { GatsbySSR } from 'gatsby';
import { getSandpackCssText } from '@codesandbox/sandpack-react';
import { THEME_NO_FLASH_SCRIPT } from './src/utilities/theme';
const onRenderBody: GatsbySSR['onRenderBody'] = ({ setHeadComponents }) => {
const inlineScripts: React.ReactNode[] = [];
// Set the theme class on <html> before first paint to avoid a flash of the
// wrong theme. Runs first so the class is present before any styles apply.
inlineScripts.push(<script key="theme-no-flash" dangerouslySetInnerHTML={{ __html: THEME_NO_FLASH_SCRIPT }} />);
// OneTrust consent management, inspiration taken from gatsby-google-tagmanager implementation
if (process.env.ONE_TRUST_ENABLED === 'true' && !!process.env.ONE_TRUST_DOMAIN) {
let domainId = process.env.ONE_TRUST_DOMAIN;
if (process.env.ONE_TRUST_TEST === 'true') {
domainId = `${domainId}-test`;
}
inlineScripts.push(
<script
key="one-trust-1"
async={false}
defer={false}
src="https://cdn-ukwest.onetrust.com/scripttemplates/otSDKStub.js"
data-domain-script={domainId}
/>,
);
inlineScripts.push(
<script
key="one-trust-2"
dangerouslySetInnerHTML={{
__html: `window.OptanonWrapper = function(){};`,
}}
/>,
);
}
// Sandpack CSS
inlineScripts.push(
<style
id="sandpack"
key="sandpack-css"
dangerouslySetInnerHTML={{
__html: getSandpackCssText(),
}}
/>,
);
setHeadComponents(inlineScripts);
};
type StyleComponent = React.ReactElement<
{
'data-href'?: string;
href?: string;
},
'style'
>;
const isStyleComponent = (node: React.ReactNode): node is StyleComponent =>
React.isValidElement(node) && node.type === 'style';
const getStyleHref = (node: StyleComponent): string | undefined => node.props?.['data-href'] ?? node.props?.href;
// Only Gatsby-emitted stylesheet chunks have a data-href/href; inline styles
// like Sandpack's do not, and must not be reordered or replaced.
const isExtractableStyleNode = (node: React.ReactNode): node is StyleComponent =>
isStyleComponent(node) && getStyleHref(node) !== undefined;
const isGlobalStyleNode = (node: React.ReactNode): boolean => {
if (!isExtractableStyleNode(node)) {
return false;
}
// Heroku review apps set assetPrefix, which causes Gatsby to emit absolute
// URLs. Normalize to a pathname so the regex matches both forms.
try {
const stylePathname = new URL(getStyleHref(node) ?? '', 'http://localhost').pathname;
return /^\/styles\.[a-zA-Z0-9]+\.css$/.test(stylePathname);
} catch {
return false;
}
};
/**
* Gatsby inlines all styles from the app inside a `<style/>` tag. This makes
* rendered HTML hostile to LLM/AI crawlers, which often bail before reaching
* any content because of the wall of CSS at the top of the document.
* Replacing each `<style data-href="…"/>` with a `<link rel="stylesheet"/>`
* pointing at the same already-emitted CSS file keeps styling intact while
* leaving the document body close to the top of the head.
*
* The same workaround is described in https://github.com/gatsbyjs/gatsby/issues/1526.
*
* Global styles sort first within the set of Gatsby-emitted stylesheet chunks
* to preserve cascade order; non-style head components and any inline `<style>`
* tags without a data-href/href (e.g. Sandpack) keep their original positions.
*/
const onPreRenderHTML: GatsbySSR['onPreRenderHTML'] = ({ getHeadComponents, replaceHeadComponents }) => {
if (process.env.NODE_ENV !== 'production') {
return;
}
const headComponents = getHeadComponents();
const sortedStyleComponents = headComponents
.filter(isExtractableStyleNode)
.sort((a, b) => Number(isGlobalStyleNode(b)) - Number(isGlobalStyleNode(a)));
let sortedStyleIndex = 0;
const transformedHeadComponents = headComponents.map((node) => {
if (isExtractableStyleNode(node)) {
const replacement = sortedStyleComponents[sortedStyleIndex++];
const href = getStyleHref(replacement) as string;
return <link key={href} href={href} rel="stylesheet" />;
}
return node;
});
replaceHeadComponents(transformedHeadComponents);
};
/**
* Load our user state, wrapped in the theme provider so useTheme is available
* everywhere (including pages that don't use the docs Layout).
*/
import UserContextWrapper from './src/contexts/user-context/wrap-with-provider';
import { ThemeProvider } from './src/contexts/theme-context';
const wrapRootElement: GatsbySSR['wrapRootElement'] = ({ element }) => (
<ThemeProvider>
<UserContextWrapper element={element} />
</ThemeProvider>
);
export { onRenderBody, onPreRenderHTML, wrapRootElement };