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

Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
46 changes: 22 additions & 24 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

82 changes: 75 additions & 7 deletions sandbox/react-router/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,84 @@
# Welcome to React Router!
# Welcome to React Router with Chakra UI!

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

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

## Features

- πŸš€ Server-side rendering
- πŸš€ Server-side rendering with React Router v7
- ⚑️ Hot Module Replacement (HMR)
- πŸ“¦ Asset bundling and optimization
- πŸ”„ Data loading and mutations
- πŸ”’ TypeScript by default
- πŸŽ‰ TailwindCSS for styling
- 🎨 Chakra UI with Emotion cache configured for SSR
- πŸŒ“ Dark mode support with next-themes
- πŸ“– [React Router docs](https://reactrouter.com/)

## Emotion Cache SSR Setup

This example demonstrates how to properly configure Emotion cache for
server-side rendering with React Router v7, addressing
[issue #10450](https://github.com/chakra-ui/chakra-ui/issues/10450).

### Architecture

The Emotion cache setup consists of three main parts:

#### 1. Emotion Cache Utilities (`app/emotion/`)

- **`emotion-cache.ts`** - Creates the base Emotion cache instance
- **`emotion-server.tsx`** - Server-side utilities (note: streaming limitations)
- **`emotion-client.tsx`** - Client-side cache provider and style injection
hooks

#### 2. Entry Files

- **`app/entry.server.tsx`** - Wraps server rendering with `CacheProvider`
- **`app/entry.client.tsx`** - Wraps client hydration with `ClientCacheProvider`

#### 3. Root Layout

- **`app/root.tsx`** - Uses `withEmotionCache` HOC and includes emotion
insertion point

### Key Implementation Details

1. **Server Rendering**: The server wraps the React Router `<ServerRouter>` with
Emotion's `<CacheProvider>` to ensure styles are tracked during SSR.

2. **Client Hydration**: The client uses `<ClientCacheProvider>` to maintain a
consistent cache across hydration and subsequent client-side navigation.

3. **Emotion Insertion Point**: A `<meta name="emotion-insertion-point">` tag is
placed in the `<head>` to control where Emotion injects styles.

4. **Style Injection**: The `useInjectStyles` hook ensures server-rendered
styles are properly transferred to the client-side cache during hydration.

5. **Hydration Warning Fix**: The `<html>` tag includes
`suppressHydrationWarning` to prevent warnings from next-themes modifying the
className during client-side rendering.

### Known Limitations

**Emotion + React 18 Streaming SSR**: Emotion doesn't fully support React 18's
`renderToPipeableStream` API yet. This implementation provides a working
solution by:

- Using `CacheProvider` on both server and client
- Relying on client-side style injection during hydration
- Maintaining style consistency through the emotion insertion point

While this doesn't provide optimal critical CSS extraction during streaming, it
prevents hydration mismatches and ensures styles render correctly.

For more information, see:

- [Emotion Discussion #2859](https://github.com/emotion-js/emotion/discussions/2859)
- [Emotion Issue #2800](https://github.com/emotion-js/emotion/issues/2800)

## Getting Started

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

## Styling

This template comes with [Tailwind CSS](https://tailwindcss.com/) already
configured for a simple default starting experience. You can use whatever CSS
framework you prefer.
This template comes with [Chakra UI](https://chakra-ui.com/) already configured
with proper Emotion cache SSR support. The styling system includes:

- Component library with accessible primitives
- Dark mode support via next-themes
- Emotion-based styling with SSR hydration
- Responsive design utilities

---

Expand Down
5 changes: 5 additions & 0 deletions sandbox/react-router/app/emotion/emotion-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import createCache from "@emotion/cache"

export function createEmotionCache() {
return createCache({ key: "css" })
}
72 changes: 72 additions & 0 deletions sandbox/react-router/app/emotion/emotion-client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { CacheProvider } from "@emotion/react"
import type { EmotionCache } from "@emotion/react"
import {
createContext,
useContext,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react"
import { createEmotionCache } from "./emotion-cache"

export interface ClientStyleContextData {
reset: () => void
}

export const ClientStyleContext = createContext<ClientStyleContextData>({
reset: () => {},
})

export const useClientStyleContext = () => {
return useContext(ClientStyleContext)
}

interface ClientCacheProviderProps {
children: React.ReactNode
}

export function ClientCacheProvider({ children }: ClientCacheProviderProps) {
const [cache, setCache] = useState(createEmotionCache())

const context = useMemo(
() => ({
reset() {
setCache(createEmotionCache())
},
}),
[],
)

return (
<ClientStyleContext.Provider value={context}>
<CacheProvider value={cache}>{children}</CacheProvider>
</ClientStyleContext.Provider>
)
}

const useSafeLayoutEffect =
typeof window === "undefined" ? () => {} : useLayoutEffect

export function useInjectStyles(cache: EmotionCache) {
const styles = useClientStyleContext()
const injectRef = useRef(true)

useSafeLayoutEffect(() => {
if (!injectRef.current) return

cache.sheet.container = document.head

const tags = cache.sheet.tags
cache.sheet.flush()
tags.forEach((tag) => {
const sheet = cache.sheet as unknown as {
_insertTag: (tag: HTMLStyleElement) => void
}
sheet._insertTag(tag)
})

styles.reset()
injectRef.current = false
}, [])
}
45 changes: 45 additions & 0 deletions sandbox/react-router/app/emotion/emotion-server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CacheProvider } from "@emotion/react"
import createEmotionServer from "@emotion/server/create-instance"
import { renderToString } from "react-dom/server"
import { Provider } from "../components/ui/provider"
import { createEmotionCache } from "./emotion-cache"

export function createEmotion() {
const cache = createEmotionCache()
const server = createEmotionServer(cache)

function injectStyles(html: string) {
const { styles } = server.extractCriticalToChunks(html)

let stylesHTML = ""

styles.forEach(({ key, ids, css }) => {
const emotionKey = `${key} ${ids.join(" ")}`
const newStyleTag = `<style data-emotion="${emotionKey}">${css}</style>`
stylesHTML = `${stylesHTML}${newStyleTag}`
})

// add the emotion style tags after the insertion point meta tag
const markup = html.replace(
/<meta(\s)*name="emotion-insertion-point"(\s)*content="emotion-insertion-point"(\s)*\/>/,
`<meta name="emotion-insertion-point" content="emotion-insertion-point"/>${stylesHTML}`,
)

return markup
}

function _renderToString(element: React.ReactNode) {
return renderToString(
<CacheProvider value={cache}>
<Provider>{element}</Provider>
</CacheProvider>,
)
}

return {
server,
cache,
injectStyles,
renderToString: _renderToString,
}
}
15 changes: 15 additions & 0 deletions sandbox/react-router/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { StrictMode, startTransition } from "react"
import { hydrateRoot } from "react-dom/client"
import { HydratedRouter } from "react-router/dom"
import { ClientCacheProvider } from "./emotion/emotion-client"

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<ClientCacheProvider>
<HydratedRouter />
</ClientCacheProvider>
</StrictMode>,
)
})
Loading