Skip to content

Commit 92a0237

Browse files
Fix theming with SSR with CSRF safe approach (#1913)
* move to application/json approach * add changelog * fix typo in getServerHandoff * Cole's suggestion to clean up code ✨ Co-authored-by: Cole Bemis <colebemis@github.com> * fix linting issues Co-authored-by: Cole Bemis <colebemis@github.com>
1 parent ab47748 commit 92a0237

File tree

2 files changed

+23
-6
lines changed

2 files changed

+23
-6
lines changed

.changeset/safe-ssr-theming.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
Fixes the theming implementation with server side rendering to use a CSRF safe approach

src/ThemeProvider.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ const ThemeContext = React.createContext<{
3737
setNightScheme: () => null
3838
})
3939

40+
// inspired from __NEXT_DATA__, we use application/json to avoid CSRF policy with inline scripts
41+
const getServerHandoff = () => {
42+
try {
43+
const serverData = document.getElementById('__PRIMER_DATA__')?.textContent
44+
if (serverData) return JSON.parse(serverData)
45+
} catch (error) {
46+
// if document/element does not exist or JSON is invalid, supress error
47+
}
48+
return {}
49+
}
50+
4051
export const ThemeProvider: React.FC<ThemeProviderProps> = ({children, ...props}) => {
4152
// Get fallback values from parent ThemeProvider (if exists)
4253
const {
@@ -49,11 +60,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({children, ...props}
4960
// Initialize state
5061
const theme = props.theme ?? fallbackTheme ?? defaultTheme
5162

52-
const resolvedColorModePassthrough = React.useRef(
53-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
54-
// @ts-ignore This custom variable does not exist on window because we set it ourselves
55-
typeof window !== 'undefined' ? window.__PRIMER_RESOLVED_SERVER_COLOR_MODE : undefined
56-
)
63+
const {resolvedServerColorMode} = getServerHandoff()
64+
const resolvedColorModePassthrough = React.useRef(resolvedServerColorMode)
5765

5866
const [colorMode, setColorMode] = React.useState(props.colorMode ?? fallbackColorMode ?? defaultColorMode)
5967
const [dayScheme, setDayScheme] = React.useState(props.dayScheme ?? fallbackDayScheme ?? defaultDayScheme)
@@ -119,7 +127,11 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({children, ...props}
119127
<SCThemeProvider theme={resolvedTheme}>
120128
{children}
121129
{props.preventSSRMismatch ? (
122-
<script dangerouslySetInnerHTML={{__html: `__PRIMER_RESOLVED_SERVER_COLOR_MODE='${resolvedColorMode}'`}} />
130+
<script
131+
type="application/json"
132+
id="__PRIMER_DATA__"
133+
dangerouslySetInnerHTML={{__html: JSON.stringify({resolvedServerColorMode: resolvedColorMode})}}
134+
/>
123135
) : null}
124136
</SCThemeProvider>
125137
</ThemeContext.Provider>

0 commit comments

Comments
 (0)