-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(public-docsite-v9): add react-router/remix ssr setup guide (#33408)
- Loading branch information
1 parent
592b73f
commit f6461c1
Showing
1 changed file
with
227 additions
and
0 deletions.
There are no files selected for viewing
227 changes: 227 additions & 0 deletions
227
apps/public-docsite-v9/src/Concepts/SSR/Remix.stories.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
import { Meta } from '@storybook/addon-docs'; | ||
|
||
<Meta title="Concepts/Developer/Server-Side Rendering/React Router 7 and Remix setup" /> | ||
|
||
# React Router 7/Remix setup | ||
|
||
## Installation | ||
|
||
1. Create a new React Router 7/Remix project or skip this step if you already have one: | ||
|
||
```bash | ||
npx create-remix@latest fluentui-remix | ||
|
||
# or | ||
npx create-react-router@latest fluentui-react-router | ||
``` | ||
|
||
2. Install dependencies: | ||
|
||
```bash | ||
# Install Fluent UI core packages | ||
npm i @fluentui/react-components @fluentui/react-icons | ||
|
||
# Install required Vite plugins | ||
npm i vite-plugin-cjs-interop @griffel/vite-plugin -D | ||
``` | ||
|
||
## Configuration | ||
|
||
1. Update `vite.config.ts`: | ||
|
||
```ts | ||
// Import Vite plugins | ||
import { cjsInterop } from 'vite-plugin-cjs-interop'; | ||
import griffel from '@griffel/vite-plugin'; | ||
|
||
export default defineConfig(({ command }) => ({ | ||
plugins: [ | ||
reactRouter(), // or remix(), | ||
tsconfigPaths(), | ||
|
||
// Add CJS interop plugin for Fluent UI packages until they are ESM compatible | ||
cjsInterop({ | ||
dependencies: ['@fluentui/react-components'], | ||
}), | ||
// Add Griffel plugin for production optimization | ||
command === 'build' && griffel(), | ||
], | ||
// Required for Fluent UI icons in SSR | ||
ssr: { | ||
noExternal: ['@fluentui/react-icons'], | ||
}, | ||
})); | ||
``` | ||
|
||
2. Modify `app/root.tsx` to add Fluent UI providers: | ||
|
||
```tsx | ||
// 1. Import Fluent UI dependencies | ||
import { FluentProvider, webLightTheme } from '@fluentui/react-components'; | ||
|
||
export function Layout({ children }: { children: React.ReactNode }) { | ||
return ( | ||
<html lang="en"> | ||
<head> | ||
<meta charSet="utf-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<Meta /> | ||
<Links /> | ||
{/* 2. Add insertion point for Fluent UI styles before the </head>. */} | ||
<meta name="fluentui-insertion-point" content="fluentui-insertion-point" /> | ||
</head> | ||
<body> | ||
{/* 3. Wrap app content with FluentProvider */} | ||
<FluentProvider theme={webLightTheme}>{children}</FluentProvider> | ||
<ScrollRestoration /> | ||
<Scripts /> | ||
</body> | ||
</html> | ||
); | ||
} | ||
``` | ||
|
||
3. Set up SSR: | ||
|
||
- Reveal `app/entry.client.tsx` and `app/entry.server.tsx` files if not already present: | ||
|
||
```bash | ||
npx react-router reveal | ||
|
||
# or | ||
|
||
npx remix reveal | ||
``` | ||
|
||
- and then update the `entry.server.tsx`: | ||
|
||
```tsx | ||
// 1. Import required Fluent UI SSR utilities | ||
import { createDOMRenderer, RendererProvider, renderToStyleElements, SSRProvider } from '@fluentui/react-components'; | ||
|
||
// 2. Define constants for style injection | ||
const FLUENT_UI_INSERTION_POINT_TAG = `<meta name="fluentui-insertion-point" content="fluentui-insertion-point"/>`; | ||
const FLUENT_UI_INSERTION_TAG_REGEX = new RegExp(FLUENT_UI_INSERTION_POINT_TAG.replaceAll(' ', '(\\s)*')); | ||
|
||
export default function handleRequest( | ||
request: Request, | ||
responseStatusCode: number, | ||
responseHeaders: Headers, | ||
remixContext: EntryContext, | ||
) { | ||
// 3. Create Fluent UI renderer | ||
const renderer = createDOMRenderer(); | ||
|
||
// ... | ||
|
||
return new Promise((resolve, reject) => { | ||
let shellRendered = false; | ||
// 4. Track style extraction state | ||
let isStyleExtracted = false; | ||
|
||
const { pipe, abort } = renderToPipeableStream( | ||
// 5. Wrap RemixServer with Fluent UI providers | ||
<RendererProvider renderer={renderer}> | ||
<SSRProvider> | ||
<ServerRouter context={routerContext} url={request.url} abortDelay={ABORT_DELAY} /> | ||
{/* or <RemixServer context={remixContext} url={request.url} /> */} | ||
</SSRProvider> | ||
</RendererProvider>, | ||
{ | ||
[callbackName]: () => { | ||
shellRendered = true; | ||
const body = new PassThrough({ | ||
// 6. Transform stream to inject Fluent UI styles | ||
transform(chunk, _, callback) { | ||
const str = chunk.toString(); | ||
const style = renderToStaticMarkup(<>{renderToStyleElements(renderer)}</>); | ||
|
||
if (!isStyleExtracted && FLUENT_UI_INSERTION_TAG_REGEX.test(str)) { | ||
chunk = str.replace(FLUENT_UI_INSERTION_TAG_REGEX, `${FLUENT_UI_INSERTION_POINT_TAG}${style}`); | ||
isStyleExtracted = true; | ||
} | ||
|
||
callback(null, chunk); | ||
}, | ||
}); | ||
// ... | ||
} | ||
} | ||
}); | ||
} | ||
``` | ||
## Usage Example | ||
Create or update `app/routes/_index.tsx`: | ||
```tsx | ||
import { Button, Card, Title1, Body1 } from '@fluentui/react-components'; | ||
import { BookmarkRegular } from '@fluentui/react-icons'; | ||
|
||
export default function Index() { | ||
return ( | ||
<Card style={{ maxWidth: '400px', margin: '20px' }}> | ||
<Title1>Fluent UI + Remix</Title1> | ||
<Body1>Welcome to your new app!</Body1> | ||
<Button appearance="primary" icon={<BookmarkRegular />}> | ||
Click me | ||
</Button> | ||
</Card> | ||
); | ||
} | ||
``` | ||
## Troubleshooting | ||
### Common Issues | ||
1. **SSR Hydration Mismatch** | ||
``` | ||
Text content does not match server-rendered HTML | ||
``` | ||
Fix: Check style injection in `entry.server.tsx`. | ||
2. **Icons Not Rendering in SSR** | ||
``` | ||
Error: No "exports" main defined in node_modules/@fluentui/react-icons/package.json | ||
``` | ||
Fix: Add to `vite.config.ts`: | ||
```ts | ||
ssr: { | ||
noExternal: ["@fluentui/react-icons"], | ||
} | ||
``` | ||
3. **Module Resolution Errors** | ||
``` | ||
Cannot use import statement outside a module | ||
``` | ||
Fix: Add to `vite.config.ts`: | ||
```ts | ||
cjsInterop({ | ||
dependencies: ["@fluentui/react-components"], | ||
}), | ||
``` | ||
4. **Development Mode Warning** | ||
``` | ||
@fluentui/react-provider: There are conflicting ids in your DOM. | ||
Please make sure that you configured your application properly. | ||
Configuration guide: https://aka.ms/fluentui-conflicting-ids | ||
``` | ||
This warning occurs in development due to [React's StrictMode double rendering](https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-double-rendering-in-development). It can be safely ignored as it doesn't affect production builds. | ||
### Production Build Optimization | ||
For production builds, install and configure [`@griffel/vite-plugin`](https://griffel.js.org/react/ahead-of-time-compilation/with-vite) to enable build time pre-computing and transforming styles. |