Install
$ agentstack add skill-secondsky-claude-skills-bun-react-ssr ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ● Shell / process execution Used
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Bun React SSR
Build custom server-rendered React applications with Bun.
Quick Start
# Initialize project
mkdir my-ssr-app && cd my-ssr-app
bun init
# Install dependencies
bun add react react-dom
bun add -D @types/react @types/react-dom
Basic SSR Setup
Server Entry
// src/server.tsx
import { renderToString } from "react-dom/server";
import App from "./App";
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
// Serve static files
if (url.pathname.startsWith("/static/")) {
const file = Bun.file(`./public${url.pathname}`);
if (await file.exists()) {
return new Response(file);
}
}
// Render React app
const html = renderToString();
return new Response(
`
React SSR
${html}
`,
{
headers: { "Content-Type": "text/html" },
}
);
},
});
console.log("Server running on http://localhost:3000");
Client Entry
// src/client.tsx
import { hydrateRoot } from "react-dom/client";
import App from "./App";
hydrateRoot(
document.getElementById("root")!,
);
React App
// src/App.tsx
interface AppProps {
url: string;
}
export default function App({ url }: AppProps) {
return (
React SSR with Bun
Current path: {url}
alert("Hydrated!")}>Click me
);
}
Build Client Bundle
// build.ts
await Bun.build({
entrypoints: ["./src/client.tsx"],
outdir: "./public/static",
target: "browser",
minify: true,
splitting: true,
});
# Build client
bun run build.ts
# Start server
bun run src/server.tsx
Streaming SSR
// src/server-streaming.tsx
import { renderToReadableStream } from "react-dom/server";
import App from "./App";
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
const stream = await renderToReadableStream(
,
{
bootstrapScripts: ["/static/client.js"],
onError(error) {
console.error(error);
},
}
);
// Wait for shell to be ready (Suspense boundaries)
await stream.allReady;
return new Response(stream, {
headers: { "Content-Type": "text/html" },
});
},
});
With Suspense
// src/App.tsx
import { Suspense } from "react";
function SlowComponent() {
// This would be a data fetching component
return Loaded!;
}
export default function App({ url }: { url: string }) {
return (
Streaming SSR
Fast Shell
Loading...}>
);
}
Data Fetching
Server-Side Data
// src/server.tsx
import { renderToString } from "react-dom/server";
import { Database } from "bun:sqlite";
import App from "./App";
const db = new Database("data.sqlite");
Bun.serve({
async fetch(req) {
const url = new URL(req.url);
// Fetch data server-side
const users = db.query("SELECT * FROM users").all();
const html = renderToString(
);
return new Response(
`
SSR
${html}
window.__INITIAL_DATA__ = ${JSON.stringify({ users })};
`,
{ headers: { "Content-Type": "text/html" } }
);
},
});
Client Hydration
// src/client.tsx
import { hydrateRoot } from "react-dom/client";
import App from "./App";
const initialData = (window as any).__INITIAL_DATA__;
hydrateRoot(
document.getElementById("root")!,
);
Routing
Simple Router
// src/Router.tsx
import { useState, useEffect } from "react";
interface Route {
path: string;
component: React.ComponentType;
}
interface RouterProps {
routes: Route[];
initialPath: string;
}
export function Router({ routes, initialPath }: RouterProps) {
const [path, setPath] = useState(initialPath);
useEffect(() => {
const handlePopState = () => setPath(window.location.pathname);
window.addEventListener("popstate", handlePopState);
return () => window.removeEventListener("popstate", handlePopState);
}, []);
const route = routes.find((r) => r.path === path);
const Component = route?.component || NotFound;
return ;
}
export function Link({ href, children }: { href: string; children: React.ReactNode }) {
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
window.history.pushState({}, "", href);
window.dispatchEvent(new PopStateEvent("popstate"));
};
return {children};
}
function NotFound() {
return 404 - Not Found;
}
CSS Handling
Inline Styles
const html = renderToString();
return new Response(
`
${await Bun.file("./src/styles.css").text()}
${html}
`,
{ headers: { "Content-Type": "text/html" } }
);
External Stylesheet
// Build CSS
await Bun.build({
entrypoints: ["./src/styles.css"],
outdir: "./public/static",
});
// Link in HTML
``
Development Setup
Hot Reload Development
// dev.ts
import { watch } from "fs";
const srcDir = "./src";
let serverProcess: Subprocess | null = null;
async function startServer() {
serverProcess?.kill();
serverProcess = Bun.spawn(["bun", "run", "src/server.tsx"], {
stdout: "inherit",
stderr: "inherit",
});
}
// Watch for changes
watch(srcDir, { recursive: true }, async (event, filename) => {
console.log(`Change detected: ${filename}`);
await startServer();
});
await startServer();
console.log("Dev server watching...");
Production Build
// build-prod.ts
// Build client
await Bun.build({
entrypoints: ["./src/client.tsx"],
outdir: "./dist/public/static",
target: "browser",
minify: true,
splitting: true,
sourcemap: "external",
});
// Build server
await Bun.build({
entrypoints: ["./src/server.tsx"],
outdir: "./dist",
target: "bun",
minify: true,
});
console.log("Build complete!");
Common Errors
| Error | Cause | Fix | |-------|-------|-----| | Hydration mismatch | Server/client HTML differs | Check initial state | | document is not defined | SSR accessing DOM | Guard with typeof window | | Cannot use hooks | Hooks outside component | Check component structure | | Flash of unstyled content | CSS not loaded | Inline critical CSS |
Performance Tips
- Use streaming SSR for faster TTFB
- Inline critical CSS above the fold
- Code split client bundle
- Cache rendered HTML for static pages
- Use Suspense for progressive loading
When to Load References
Load references/streaming-patterns.md when:
- Complex Suspense boundaries
- Selective hydration
- Progressive enhancement
Load references/caching.md when:
- HTML caching strategies
- CDN integration
- Edge rendering
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: secondsky
- Source: secondsky/claude-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.