Skip to content
Open
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
4 changes: 4 additions & 0 deletions examples/react-router-framework-and-antd/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.react-router
build
node_modules
README.md
6 changes: 6 additions & 0 deletions examples/react-router-framework-and-antd/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
/node_modules/

# React Router
/.react-router/
/build/
22 changes: 22 additions & 0 deletions examples/react-router-framework-and-antd/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM node:20-alpine AS development-dependencies-env
COPY . /app
WORKDIR /app
RUN npm ci

FROM node:20-alpine AS production-dependencies-env
COPY ./package.json package-lock.json /app/
WORKDIR /app
RUN npm ci --omit=dev

FROM node:20-alpine AS build-env
COPY . /app/
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
WORKDIR /app
RUN npm run build

FROM node:20-alpine
COPY ./package.json package-lock.json /app/
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
WORKDIR /app
CMD ["npm", "run", "start"]
14 changes: 14 additions & 0 deletions examples/react-router-framework-and-antd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# React Router + TypeScript + Vite

## Development

```bash
npm install && npm run dev
```

## Docs

- [Vite](https://vitejs.dev/)
- [React](https://react.dev/)
- [Ant Design](https://ant.design/)
- [React Router docs](https://reactrouter.com/)
15 changes: 15 additions & 0 deletions examples/react-router-framework-and-antd/app/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import "tailwindcss";

@theme {
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}

html,
body {
@apply bg-white dark:bg-gray-950;

@media (prefers-color-scheme: dark) {
color-scheme: dark;
}
}
18 changes: 18 additions & 0 deletions examples/react-router-framework-and-antd/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createCache, StyleProvider } from "@ant-design/cssinjs";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";

// used to ensure proper hydration and consistent styling between server and client for Ant Design components
const cache = createCache();

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<StyleProvider cache={cache}>
<HydratedRouter />
</StyleProvider>
</StrictMode>
);
});
97 changes: 97 additions & 0 deletions examples/react-router-framework-and-antd/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { PassThrough } from "node:stream";

import type { AppLoadContext, EntryContext } from "react-router";
import { createReadableStreamFromReadable } from "@react-router/node";
import { ServerRouter } from "react-router";
import { isbot } from "isbot";
import type { RenderToPipeableStreamOptions } from "react-dom/server";
import { renderToPipeableStream } from "react-dom/server";
import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs";

export const streamTimeout = 5_000;

export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
routerContext: EntryContext,
loadContext: AppLoadContext
// If you have middleware enabled:
// loadContext: unstable_RouterContextProvider
) {
return new Promise((resolve, reject) => {
let shellRendered = false;
let userAgent = request.headers.get("user-agent");
const cache = createCache();
let styles = "";

// Ensure requests from bots and SPA Mode renders wait for all content to load before responding
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
let readyOption: keyof RenderToPipeableStreamOptions =
(userAgent && isbot(userAgent)) || routerContext.isSpaMode
? "onAllReady"
: "onShellReady";

const { pipe, abort } = renderToPipeableStream(
<StyleProvider cache={cache}>
<ServerRouter context={routerContext} url={request.url} />
</StyleProvider>,
{
[readyOption]() {
shellRendered = true;
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);

responseHeaders.set("Content-Type", "text/html");

// Extract styles from the cache
styles = extractStyle(cache);

// We need to transform the output to inject the Ant Design styles
let didInjectStyles = false;
const wrappedStream = new TransformStream({
transform(chunk, controller) {
const chunkString = new TextDecoder().decode(chunk);
if (!didInjectStyles && chunkString.includes("</head>")) {
// Inject the styles before the closing head tag
const modifiedChunk = chunkString.replace(
"</head>",
`<style id="antd-styles">${styles}</style></head>`
);
didInjectStyles = true;
controller.enqueue(new TextEncoder().encode(modifiedChunk));
} else {
controller.enqueue(chunk);
}
},
});

resolve(
new Response(stream.pipeThrough(wrappedStream), {
headers: responseHeaders,
status: responseStatusCode,
})
);

pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
}
);

// Abort the rendering stream after the `streamTimeout` so it has time to
// flush down the rejected boundaries
setTimeout(abort, streamTimeout + 1000);
});
}
92 changes: 92 additions & 0 deletions examples/react-router-framework-and-antd/app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
import { ConfigProvider } from "antd";

import type { Route } from "./+types/root";
import "./app.css";

export const links: Route.LinksFunction = () => [
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossOrigin: "anonymous",
},
{
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
},
{
rel: "icon",
href: "/public/favicon.ico",
sizes: "48x48",
type: "image/vnd.microsoft.icon",
},
];

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 />
</head>
<body>
<ConfigProvider
theme={{
components: {
Button: {
colorPrimary: "#00B96B",
},
},
}}
>
{children}
</ConfigProvider>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

export default function App() {
return <Outlet />;
}

export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = "Oops!";
let details = "An unexpected error occurred.";
let stack: string | undefined;

if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error";
details =
error.status === 404
? "The requested page could not be found."
: error.statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}

return (
<main className="pt-16 p-4 container mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
);
}
3 changes: 3 additions & 0 deletions examples/react-router-framework-and-antd/app/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { type RouteConfig, index } from "@react-router/dev/routes";

export default [index("routes/home.tsx")] satisfies RouteConfig;
13 changes: 13 additions & 0 deletions examples/react-router-framework-and-antd/app/routes/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Route } from "./+types/home";
import { Welcome } from "../welcome/welcome";

export function meta({}: Route.MetaArgs) {
return [
{ title: "New React Router App" },
{ name: "description", content: "Welcome to React Router!" },
];
}

export default function Home() {
return <Welcome />;
}
23 changes: 23 additions & 0 deletions examples/react-router-framework-and-antd/app/welcome/logo-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading