AgentStack
SKILL verified MIT Self-run

Bun React Ssr

skill-secondsky-claude-skills-bun-react-ssr · by secondsky

Use when building server-rendered React with Bun, including streaming SSR, hydration, renderToString, or custom SSR without a framework.

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add skill-secondsky-claude-skills-bun-react-ssr

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

Are you the author of Bun React Ssr? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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

  1. Use streaming SSR for faster TTFB
  2. Inline critical CSS above the fold
  3. Code split client bundle
  4. Cache rendered HTML for static pages
  5. 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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.