Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/safe-ssr-theming.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Fixes the theming implementation with server side rendering to use a CSRF safe approach
25 changes: 19 additions & 6 deletions src/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ const ThemeContext = React.createContext<{
setNightScheme: () => null
})

// inspired from __NEXT_DATA__, we use application/json to avoid CSRF policy with inline scripts
const getSeverHandoff = () => {
if (typeof document !== 'undefined' && document.getElementById('__PRIMER_DATA__')?.textContent) {
try {
return JSON.parse(document.getElementById('__PRIMER_DATA__')?.textContent as string)
} catch (error) {
// if JSON is invalid, supress error
}
}
return {}
}

export const ThemeProvider: React.FC<ThemeProviderProps> = ({children, ...props}) => {
// Get fallback values from parent ThemeProvider (if exists)
const {
Expand All @@ -49,11 +61,8 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({children, ...props}
// Initialize state
const theme = props.theme ?? fallbackTheme ?? defaultTheme

const resolvedColorModePassthrough = React.useRef(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This custom variable does not exist on window because we set it ourselves
typeof window !== 'undefined' ? window.__PRIMER_RESOLVED_SERVER_COLOR_MODE : undefined
)
const {resolvedServerColorMode} = getSeverHandoff()
const resolvedColorModePassthrough = React.useRef(resolvedServerColorMode)

const [colorMode, setColorMode] = React.useState(props.colorMode ?? fallbackColorMode ?? defaultColorMode)
const [dayScheme, setDayScheme] = React.useState(props.dayScheme ?? fallbackDayScheme ?? defaultDayScheme)
Expand Down Expand Up @@ -119,7 +128,11 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({children, ...props}
<SCThemeProvider theme={resolvedTheme}>
{children}
{props.preventSSRMismatch ? (
<script dangerouslySetInnerHTML={{__html: `__PRIMER_RESOLVED_SERVER_COLOR_MODE='${resolvedColorMode}'`}} />
<script
type="application/json"
id="__PRIMER_DATA__"
dangerouslySetInnerHTML={{__html: JSON.stringify({resolvedServerColorMode: resolvedColorMode})}}
/>
) : null}
</SCThemeProvider>
</ThemeContext.Provider>
Expand Down