Skip to content

Commit f453cee

Browse files
committed
basic structure
1 parent d558cc3 commit f453cee

File tree

10 files changed

+1328
-230
lines changed

10 files changed

+1328
-230
lines changed

package-lock.json

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

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"react": "^19.1.0",
4242
"react-dom": "^19.1.0",
4343
"react-markdown": "^10.1.0",
44+
"react-router": "^7.7.1",
4445
"rehype-slug": "^6.0.0",
4546
"rehype-stringify": "^10.0.1",
4647
"remark-comment": "^1.0.0",
@@ -57,7 +58,7 @@
5758
"vfile-matter": "^5.0.0"
5859
},
5960
"scripts": {
60-
"dev": "next",
61+
"dev": "vite",
6162
"res:watch": "rescript build -w",
6263
"build": "rescript && npm run update-index && next build",
6364
"test": "node scripts/test-examples.mjs && node scripts/test-hrefs.mjs",
@@ -69,6 +70,8 @@
6970
},
7071
"devDependencies": {
7172
"@mdx-js/react": "^2.3.0",
73+
"@vitejs/plugin-react": "^4.7.0",
74+
"@vitejs/plugin-rsc": "^0.4.16",
7275
"autoprefixer": "^10.4.14",
7376
"cssnano": "^6.0.1",
7477
"dotenv": "^16.4.7",
@@ -79,6 +82,7 @@
7982
"postcss-nesting": "^12.1.1",
8083
"reanalyze": "^2.16.0",
8184
"simple-functional-loader": "^1.2.1",
82-
"tailwindcss": "^3.3.3"
85+
"tailwindcss": "^3.3.3",
86+
"vite": "^7.0.6"
8387
}
84-
}
88+
}

routes/config.mjs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
export function routes() {
3+
return [
4+
{
5+
id: "root",
6+
path: "",
7+
lazy: () => import("./root/route.jsx"),
8+
children: [
9+
{
10+
id: "home",
11+
index: true,
12+
lazy: () => import("./home/route.jsx")
13+
}
14+
],
15+
},
16+
]
17+
}

routes/home/route.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { make as LandingPageLayout } from "../../src/layouts/LandingPageLayout.mjs";
2+
3+
export default function Home() {
4+
return <LandingPageLayout />
5+
}

routes/root/client.jsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"use client";
2+
3+
import {
4+
isRouteErrorResponse,
5+
Link,
6+
NavLink,
7+
useNavigation,
8+
useRouteError,
9+
} from "react-router";
10+
11+
export function Layout({ children }) {
12+
const navigation = useNavigation();
13+
return (
14+
<html lang="en">
15+
<head>
16+
<meta charSet="utf-8" />
17+
<meta name="viewport" content="width=device-width, initial-scale=1" />
18+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
19+
</head>
20+
<body className="font-sans antialiased">
21+
<header className="sticky inset-x-0 top-0 z-50 bg-background border-b">
22+
<div className="mx-auto max-w-screen-xl px-4 relative flex h-16 items-center justify-between gap-4 sm:gap-8">
23+
<div className="flex items-center gap-4">
24+
<Link to="/">React Router 🚀</Link>
25+
<nav>
26+
<ul className="gap-4 flex">
27+
<li>
28+
<NavLink
29+
to="/"
30+
className="text-sm font-medium hover:opacity-75 aria-[current]:opacity-75"
31+
>
32+
Home
33+
</NavLink>
34+
</li>
35+
<li>
36+
<NavLink
37+
to="/about"
38+
className="text-sm font-medium hover:opacity-75 aria-[current]:opacity-75"
39+
>
40+
About
41+
</NavLink>
42+
</li>
43+
</ul>
44+
</nav>
45+
<div>{navigation.state !== "idle" && <p>Loading...</p>}</div>
46+
</div>
47+
</div>
48+
</header>
49+
{children}
50+
</body>
51+
</html>
52+
);
53+
}
54+
55+
export function ErrorBoundary() {
56+
const error = useRouteError();
57+
let status = 500;
58+
let message = "An unexpected error occurred.";
59+
60+
if (isRouteErrorResponse(error)) {
61+
status = error.status;
62+
message = status === 404 ? "Page not found." : error.statusText || message;
63+
}
64+
65+
return (
66+
<main className="mx-auto max-w-screen-xl px-4 py-8 lg:py-12">
67+
<article className="prose mx-auto">
68+
<h1>{status}</h1>
69+
<p>{message}</p>
70+
</article>
71+
</main>
72+
);
73+
}

routes/root/route.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Outlet } from "react-router";
2+
3+
import "../../styles/main.css";
4+
import "../../styles/utils.css";
5+
// import "codemirror/lib/codemirror.css";
6+
// import "styles/cm.css";
7+
// import "styles/docson.css";
8+
9+
import { Layout as ClientLayout } from "./client";
10+
11+
export { ErrorBoundary } from "./client";
12+
13+
export function Layout({ children }) {
14+
// This is necessary for the bundler to inject the needed CSS assets.
15+
return <ClientLayout>{children}</ClientLayout>;
16+
}
17+
18+
export default function Component() {
19+
return <Outlet />;
20+
}

src/entry.browser.jsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {
2+
createFromReadableStream,
3+
createTemporaryReferenceSet,
4+
encodeReply,
5+
setServerCallback,
6+
} from "@vitejs/plugin-rsc/browser";
7+
import { startTransition, StrictMode } from "react";
8+
import { hydrateRoot } from "react-dom/client";
9+
import {
10+
unstable_createCallServer as createCallServer,
11+
unstable_getRSCStream as getRSCStream,
12+
unstable_RSCHydratedRouter as RSCHydratedRouter,
13+
} from "react-router";
14+
15+
// Create and set the callServer function to support post-hydration server actions.
16+
setServerCallback(
17+
createCallServer({
18+
createFromReadableStream,
19+
createTemporaryReferenceSet,
20+
encodeReply,
21+
}),
22+
);
23+
24+
// Get and decode the initial server payload.
25+
createFromReadableStream(
26+
getRSCStream(),
27+
).then((payload) => {
28+
startTransition(async () => {
29+
const formState =
30+
payload.type === "render"
31+
? await payload.formState
32+
: undefined;
33+
34+
hydrateRoot(
35+
document,
36+
<StrictMode>
37+
<RSCHydratedRouter
38+
createFromReadableStream={
39+
createFromReadableStream
40+
}
41+
payload={payload}
42+
/>
43+
</StrictMode>,
44+
{
45+
formState,
46+
},
47+
);
48+
});
49+
});

src/entry.rsc.jsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
createTemporaryReferenceSet,
3+
decodeAction,
4+
decodeFormState,
5+
decodeReply,
6+
loadServerAction,
7+
renderToReadableStream,
8+
} from "@vitejs/plugin-rsc/rsc";
9+
import { unstable_matchRSCServerRequest as matchRSCServerRequest } from "react-router";
10+
11+
import { routes } from "../routes/config";
12+
13+
function fetchServer(request) {
14+
return matchRSCServerRequest({
15+
// Provide the React Server touchpoints.
16+
createTemporaryReferenceSet,
17+
decodeAction,
18+
decodeFormState,
19+
decodeReply,
20+
loadServerAction,
21+
// The incoming request.
22+
request,
23+
// The app routes.
24+
routes: routes(),
25+
// Encode the match with the React Server implementation.
26+
generateResponse(match) {
27+
return new Response(
28+
renderToReadableStream(match.payload),
29+
{
30+
status: match.statusCode,
31+
headers: match.headers,
32+
},
33+
);
34+
},
35+
});
36+
}
37+
38+
export default async function handler(request) {
39+
// Import the generateHTML function from the client environment
40+
const ssr = await import.meta.viteRsc.loadModule("ssr", "index");
41+
42+
return ssr.generateHTML(request, fetchServer);
43+
}

src/entry.ssr.jsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
2+
import { renderToReadableStream as renderHTMLToReadableStream } from "react-dom/server.edge";
3+
import {
4+
unstable_routeRSCServerRequest as routeRSCServerRequest,
5+
unstable_RSCStaticRouter as RSCStaticRouter,
6+
} from "react-router";
7+
8+
export async function generateHTML(
9+
request,
10+
fetchServer,
11+
) {
12+
return await routeRSCServerRequest({
13+
// The incoming request.
14+
request,
15+
// How to call the React Server.
16+
fetchServer,
17+
// Provide the React Server touchpoints.
18+
createFromReadableStream,
19+
// Render the router to HTML.
20+
async renderHTML(getPayload) {
21+
const payload = await getPayload();
22+
const formState =
23+
payload.type === "render"
24+
? await payload.formState
25+
: undefined;
26+
27+
const bootstrapScriptContent =
28+
await import.meta.viteRsc.loadBootstrapScriptContent(
29+
"index",
30+
);
31+
32+
return await renderHTMLToReadableStream(
33+
<RSCStaticRouter getPayload={getPayload} />,
34+
{
35+
bootstrapScriptContent,
36+
formState,
37+
},
38+
);
39+
},
40+
});
41+
}

vite.config.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import react from "@vitejs/plugin-react";
2+
import rsc from "@vitejs/plugin-rsc/plugin";
3+
import { defineConfig } from "vite";
4+
5+
export default defineConfig({
6+
plugins: [
7+
react(),
8+
rsc({
9+
entries: {
10+
client: "src/entry.browser.jsx",
11+
rsc: "src/entry.rsc.jsx",
12+
ssr: "src/entry.ssr.jsx",
13+
},
14+
}),
15+
],
16+
});

0 commit comments

Comments
 (0)