Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fdf5ec5

Browse files
committed
chore: react router example
1 parent 4d7dde1 commit fdf5ec5

File tree

9 files changed

+331
-35
lines changed

9 files changed

+331
-35
lines changed

pnpm-lock.yaml

Lines changed: 22 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sandbox/react-router/README.md

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,84 @@
1-
# Welcome to React Router!
1+
# Welcome to React Router with Chakra UI!
22

33
A modern, production-ready template for building full-stack React applications
4-
using React Router.
4+
using React Router and Chakra UI with proper Emotion cache SSR support.
55

66
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default)
77

88
## Features
99

10-
- 🚀 Server-side rendering
10+
- 🚀 Server-side rendering with React Router v7
1111
- ⚡️ Hot Module Replacement (HMR)
1212
- 📦 Asset bundling and optimization
1313
- 🔄 Data loading and mutations
1414
- 🔒 TypeScript by default
15-
- 🎉 TailwindCSS for styling
15+
- 🎨 Chakra UI with Emotion cache configured for SSR
16+
- 🌓 Dark mode support with next-themes
1617
- 📖 [React Router docs](https://reactrouter.com/)
1718

19+
## Emotion Cache SSR Setup
20+
21+
This example demonstrates how to properly configure Emotion cache for
22+
server-side rendering with React Router v7, addressing
23+
[issue #10450](https://github.com/chakra-ui/chakra-ui/issues/10450).
24+
25+
### Architecture
26+
27+
The Emotion cache setup consists of three main parts:
28+
29+
#### 1. Emotion Cache Utilities (`app/emotion/`)
30+
31+
- **`emotion-cache.ts`** - Creates the base Emotion cache instance
32+
- **`emotion-server.tsx`** - Server-side utilities (note: streaming limitations)
33+
- **`emotion-client.tsx`** - Client-side cache provider and style injection
34+
hooks
35+
36+
#### 2. Entry Files
37+
38+
- **`app/entry.server.tsx`** - Wraps server rendering with `CacheProvider`
39+
- **`app/entry.client.tsx`** - Wraps client hydration with `ClientCacheProvider`
40+
41+
#### 3. Root Layout
42+
43+
- **`app/root.tsx`** - Uses `withEmotionCache` HOC and includes emotion
44+
insertion point
45+
46+
### Key Implementation Details
47+
48+
1. **Server Rendering**: The server wraps the React Router `<ServerRouter>` with
49+
Emotion's `<CacheProvider>` to ensure styles are tracked during SSR.
50+
51+
2. **Client Hydration**: The client uses `<ClientCacheProvider>` to maintain a
52+
consistent cache across hydration and subsequent client-side navigation.
53+
54+
3. **Emotion Insertion Point**: A `<meta name="emotion-insertion-point">` tag is
55+
placed in the `<head>` to control where Emotion injects styles.
56+
57+
4. **Style Injection**: The `useInjectStyles` hook ensures server-rendered
58+
styles are properly transferred to the client-side cache during hydration.
59+
60+
5. **Hydration Warning Fix**: The `<html>` tag includes
61+
`suppressHydrationWarning` to prevent warnings from next-themes modifying the
62+
className during client-side rendering.
63+
64+
### Known Limitations
65+
66+
**Emotion + React 18 Streaming SSR**: Emotion doesn't fully support React 18's
67+
`renderToPipeableStream` API yet. This implementation provides a working
68+
solution by:
69+
70+
- Using `CacheProvider` on both server and client
71+
- Relying on client-side style injection during hydration
72+
- Maintaining style consistency through the emotion insertion point
73+
74+
While this doesn't provide optimal critical CSS extraction during streaming, it
75+
prevents hydration mismatches and ensures styles render correctly.
76+
77+
For more information, see:
78+
79+
- [Emotion Discussion #2859](https://github.com/emotion-js/emotion/discussions/2859)
80+
- [Emotion Issue #2800](https://github.com/emotion-js/emotion/issues/2800)
81+
1882
## Getting Started
1983

2084
### Installation
@@ -83,9 +147,13 @@ Make sure to deploy the output of `npm run build`
83147

84148
## Styling
85149

86-
This template comes with [Tailwind CSS](https://tailwindcss.com/) already
87-
configured for a simple default starting experience. You can use whatever CSS
88-
framework you prefer.
150+
This template comes with [Chakra UI](https://chakra-ui.com/) already configured
151+
with proper Emotion cache SSR support. The styling system includes:
152+
153+
- Component library with accessible primitives
154+
- Dark mode support via next-themes
155+
- Emotion-based styling with SSR hydration
156+
- Responsive design utilities
89157

90158
---
91159

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import createCache from "@emotion/cache"
2+
3+
export function createEmotionCache() {
4+
return createCache({ key: "css" })
5+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { CacheProvider } from "@emotion/react"
2+
import type { EmotionCache } from "@emotion/react"
3+
import {
4+
createContext,
5+
useContext,
6+
useLayoutEffect,
7+
useMemo,
8+
useRef,
9+
useState,
10+
} from "react"
11+
import { createEmotionCache } from "./emotion-cache"
12+
13+
export interface ClientStyleContextData {
14+
reset: () => void
15+
}
16+
17+
export const ClientStyleContext = createContext<ClientStyleContextData>({
18+
reset: () => {},
19+
})
20+
21+
export const useClientStyleContext = () => {
22+
return useContext(ClientStyleContext)
23+
}
24+
25+
interface ClientCacheProviderProps {
26+
children: React.ReactNode
27+
}
28+
29+
export function ClientCacheProvider({ children }: ClientCacheProviderProps) {
30+
const [cache, setCache] = useState(createEmotionCache())
31+
32+
const context = useMemo(
33+
() => ({
34+
reset() {
35+
setCache(createEmotionCache())
36+
},
37+
}),
38+
[],
39+
)
40+
41+
return (
42+
<ClientStyleContext.Provider value={context}>
43+
<CacheProvider value={cache}>{children}</CacheProvider>
44+
</ClientStyleContext.Provider>
45+
)
46+
}
47+
48+
const useSafeLayoutEffect =
49+
typeof window === "undefined" ? () => {} : useLayoutEffect
50+
51+
export function useInjectStyles(cache: EmotionCache) {
52+
const styles = useClientStyleContext()
53+
const injectRef = useRef(true)
54+
55+
useSafeLayoutEffect(() => {
56+
if (!injectRef.current) return
57+
58+
cache.sheet.container = document.head
59+
60+
const tags = cache.sheet.tags
61+
cache.sheet.flush()
62+
tags.forEach((tag) => {
63+
const sheet = cache.sheet as unknown as {
64+
_insertTag: (tag: HTMLStyleElement) => void
65+
}
66+
sheet._insertTag(tag)
67+
})
68+
69+
styles.reset()
70+
injectRef.current = false
71+
}, [])
72+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { CacheProvider } from "@emotion/react"
2+
import createEmotionServer from "@emotion/server/create-instance"
3+
import { renderToString } from "react-dom/server"
4+
import { Provider } from "../components/ui/provider"
5+
import { createEmotionCache } from "./emotion-cache"
6+
7+
export function createEmotion() {
8+
const cache = createEmotionCache()
9+
const server = createEmotionServer(cache)
10+
11+
function injectStyles(html: string) {
12+
const { styles } = server.extractCriticalToChunks(html)
13+
14+
let stylesHTML = ""
15+
16+
styles.forEach(({ key, ids, css }) => {
17+
const emotionKey = `${key} ${ids.join(" ")}`
18+
const newStyleTag = `<style data-emotion="${emotionKey}">${css}</style>`
19+
stylesHTML = `${stylesHTML}${newStyleTag}`
20+
})
21+
22+
// add the emotion style tags after the insertion point meta tag
23+
const markup = html.replace(
24+
/<meta(\s)*name="emotion-insertion-point"(\s)*content="emotion-insertion-point"(\s)*\/>/,
25+
`<meta name="emotion-insertion-point" content="emotion-insertion-point"/>${stylesHTML}`,
26+
)
27+
28+
return markup
29+
}
30+
31+
function _renderToString(element: React.ReactNode) {
32+
return renderToString(
33+
<CacheProvider value={cache}>
34+
<Provider>{element}</Provider>
35+
</CacheProvider>,
36+
)
37+
}
38+
39+
return {
40+
server,
41+
cache,
42+
injectStyles,
43+
renderToString: _renderToString,
44+
}
45+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { StrictMode, startTransition } from "react"
2+
import { hydrateRoot } from "react-dom/client"
3+
import { HydratedRouter } from "react-router/dom"
4+
import { ClientCacheProvider } from "./emotion/emotion-client"
5+
6+
startTransition(() => {
7+
hydrateRoot(
8+
document,
9+
<StrictMode>
10+
<ClientCacheProvider>
11+
<HydratedRouter />
12+
</ClientCacheProvider>
13+
</StrictMode>,
14+
)
15+
})

0 commit comments

Comments
 (0)