> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/preactjs/preact/llms.txt
> Use this file to discover all available pages before exploring further.

# Server-Side Rendering (SSR)

> Render Preact components on the server for better performance and SEO

Server-Side Rendering (SSR) lets you render Preact components to HTML on the server. This improves initial page load performance, SEO, and provides a better experience for users on slow connections.

## Why Use SSR?

<CardGroup cols={2}>
  <Card title="Better Performance" icon="gauge-high">
    Users see content faster since HTML is sent immediately instead of waiting for JavaScript to load and execute.
  </Card>

  <Card title="Improved SEO" icon="magnifying-glass">
    Search engines can index your content immediately without executing JavaScript.
  </Card>

  <Card title="Social Media" icon="share-nodes">
    Social media crawlers can read your content for previews and cards.
  </Card>

  <Card title="Accessibility" icon="universal-access">
    Content is available even if JavaScript fails to load or is disabled.
  </Card>
</CardGroup>

## Installation

SSR requires the `preact-render-to-string` package:

```bash theme={null}
npm install preact-render-to-string
```

This package provides functions to render Preact components to HTML strings.

## Basic Usage

<Tabs>
  <Tab title="renderToString">
    The most common SSR function. Renders a component tree to an HTML string:

    ```jsx theme={null}
    import { renderToString } from 'preact-render-to-string';
    import { App } from './App';

    // Render to HTML string
    const html = renderToString(<App />);

    console.log(html);
    // Output: <div class="app">...</div>
    ```

    This is a synchronous function that returns a complete HTML string.
  </Tab>

  <Tab title="renderToStaticMarkup">
    Same as `renderToString` but omits Preact-specific attributes:

    ```jsx theme={null}
    import { renderToStaticMarkup } from 'preact-render-to-string';
    import { App } from './App';

    // Render to static HTML (no hydration markers)
    const html = renderToStaticMarkup(<App />);
    ```

    Use this when you won't hydrate the HTML on the client (e.g., for emails).
  </Tab>
</Tabs>

## Integration with Servers

<CodeGroup>
  ```javascript Express.js theme={null}
  import express from 'express';
  import { renderToString } from 'preact-render-to-string';
  import { App } from './App';

  const app = express();

  app.get('*', (req, res) => {
    const html = renderToString(<App url={req.url} />);
    
    res.send(`
      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="UTF-8">
          <title>My App</title>
          <link rel="stylesheet" href="/styles.css">
        </head>
        <body>
          <div id="app">${html}</div>
          <script src="/bundle.js"></script>
        </body>
      </html>
    `);
  });

  app.listen(3000);
  ```

  ```javascript Node.js HTTP theme={null}
  import { createServer } from 'http';
  import { renderToString } from 'preact-render-to-string';
  import { App } from './App';

  const server = createServer((req, res) => {
    const html = renderToString(<App url={req.url} />);
    
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end(`
      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="UTF-8">
          <title>My App</title>
        </head>
        <body>
          <div id="app">${html}</div>
          <script src="/bundle.js"></script>
        </body>
      </html>
    `);
  });

  server.listen(3000);
  ```

  ```javascript Fastify theme={null}
  import Fastify from 'fastify';
  import { renderToString } from 'preact-render-to-string';
  import { App } from './App';

  const fastify = Fastify();

  fastify.get('*', (request, reply) => {
    const html = renderToString(<App url={request.url} />);
    
    reply.type('text/html').send(`
      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="UTF-8">
          <title>My App</title>
        </head>
        <body>
          <div id="app">${html}</div>
          <script src="/bundle.js"></script>
        </body>
      </html>
    `);
  });

  fastify.listen({ port: 3000 });
  ```
</CodeGroup>

## Hydration

After sending server-rendered HTML, you need to "hydrate" it on the client. Hydration attaches event listeners and makes the app interactive.

<Steps>
  ### Server-side: Render to string

  ```jsx theme={null}
  // server.js
  import { renderToString } from 'preact-render-to-string';
  import { App } from './App';

  const html = renderToString(<App />);
  // Send html to client...
  ```

  ### Client-side: Hydrate

  ```jsx theme={null}
  // client.js
  import { hydrate } from 'preact';
  import { App } from './App';

  // Hydrate the server-rendered HTML
  hydrate(<App />, document.getElementById('app'));
  ```

  ### How Hydrate Works

  The `hydrate` function is implemented in `src/render.js`. It reuses the existing DOM instead of creating new elements:

  ```javascript theme={null}
  // From src/render.js:66-70
  export function hydrate(vnode, parentDom) {
    vnode._flags |= MODE_HYDRATE;
    render(vnode, parentDom);
  }
  ```

  Hydration sets a flag that tells the diffing algorithm to preserve existing DOM nodes.

  Reference: `src/render.js:66-70`
</Steps>

<Warning>
  The component tree must be identical on server and client, or hydration will fail and Preact will re-render from scratch.
</Warning>

## Streaming SSR

For large applications, streaming can improve Time To First Byte (TTFB):

<Tabs>
  <Tab title="renderToReadableStream (Web)">
    Returns a ReadableStream for use in modern web environments:

    ```javascript theme={null}
    import { renderToReadableStream } from 'preact-render-to-string/stream';
    import { App } from './App';

    // Modern web server (e.g., Cloudflare Workers)
    export default {
      async fetch(request) {
        const stream = await renderToReadableStream(<App />);
        
        return new Response(stream, {
          headers: { 'Content-Type': 'text/html' }
        });
      }
    };
    ```
  </Tab>

  <Tab title="renderToPipeableStream (Node)">
    Returns a pipeable stream for Node.js:

    ```javascript theme={null}
    import { renderToPipeableStream } from 'preact-render-to-string/stream-node';
    import { App } from './App';

    app.get('*', (req, res) => {
      res.setHeader('Content-Type', 'text/html');
      
      const stream = renderToPipeableStream(<App />);
      
      stream.pipe(res);
    });
    ```
  </Tab>
</Tabs>

## Compat Layer

When using `preact/compat`, SSR functions are re-exported for React compatibility:

```javascript theme={null}
// From compat/server.d.ts
import { 
  renderToString,
  renderToStaticMarkup,
  renderToPipeableStream,
  renderToReadableStream 
} from 'preact-render-to-string';
```

Reference: `compat/server.d.ts`

You can import from `preact/compat/server` just like `react-dom/server`:

```javascript theme={null}
import { renderToString } from 'preact/compat/server';
```

## Data Fetching

For SSR with data fetching, you'll need to fetch data on the server before rendering:

<CodeGroup>
  ```javascript Basic Data Fetching theme={null}
  import express from 'express';
  import { renderToString } from 'preact-render-to-string';
  import { App } from './App';

  const app = express();

  app.get('/user/:id', async (req, res) => {
    // Fetch data on server
    const user = await fetch(`https://api.example.com/users/${req.params.id}`)
      .then(r => r.json());
    
    // Pass data to component
    const html = renderToString(<App user={user} />);
    
    res.send(`
      <!DOCTYPE html>
      <html>
        <body>
          <div id="app">${html}</div>
          <script>
            window.__INITIAL_DATA__ = ${JSON.stringify({ user })};
          </script>
          <script src="/bundle.js"></script>
        </body>
      </html>
    `);
  });
  ```

  ```javascript Client Hydration with Data theme={null}
  import { hydrate } from 'preact';
  import { App } from './App';

  // Get initial data from server
  const initialData = window.__INITIAL_DATA__;

  // Hydrate with same data
  hydrate(
    <App user={initialData.user} />,
    document.getElementById('app')
  );

  // Clean up
  delete window.__INITIAL_DATA__;
  ```

  ```javascript Context-based Data theme={null}
  import { createContext } from 'preact';
  import { useContext } from 'preact/hooks';

  const DataContext = createContext(null);

  // Server
  const data = await fetchData();
  const html = renderToString(
    <DataContext.Provider value={data}>
      <App />
    </DataContext.Provider>
  );

  // Component
  function UserProfile() {
    const data = useContext(DataContext);
    return <div>{data.user.name}</div>;
  }

  // Client
  const initialData = window.__INITIAL_DATA__;
  hydrate(
    <DataContext.Provider value={initialData}>
      <App />
    </DataContext.Provider>,
    document.getElementById('app')
  );
  ```
</CodeGroup>

## Best Practices

<Steps>
  ### Avoid side effects during SSR

  Don't use browser-only APIs during rendering:

  ```jsx theme={null}
  // Bad: localStorage is not available on server
  function Component() {
    const theme = localStorage.getItem('theme');
    return <div className={theme}>Content</div>;
  }

  // Good: Check for browser environment
  import { useState, useEffect } from 'preact/hooks';

  function Component() {
    const [theme, setTheme] = useState('light');
    
    useEffect(() => {
      // Only runs on client
      const savedTheme = localStorage.getItem('theme');
      if (savedTheme) setTheme(savedTheme);
    }, []);
    
    return <div className={theme}>Content</div>;
  }
  ```

  ### Use useEffect for browser-only code

  `useEffect` only runs on the client, making it safe for browser APIs:

  ```jsx theme={null}
  import { useEffect, useState } from 'preact/hooks';

  function Component() {
    const [width, setWidth] = useState(0);
    
    useEffect(() => {
      // Safe: only runs on client
      setWidth(window.innerWidth);
      
      const handleResize = () => setWidth(window.innerWidth);
      window.addEventListener('resize', handleResize);
      return () => window.removeEventListener('resize', handleResize);
    }, []);
    
    return <div>Width: {width || 'calculating...'}px</div>;
  }
  ```

  ### Serialize data carefully

  When passing data from server to client, avoid XSS vulnerabilities:

  ```javascript theme={null}
  // Bad: Vulnerable to XSS
  const html = `
    <script>
      window.__DATA__ = ${JSON.stringify(userData)};
    </script>
  `;

  // Good: Escape HTML in JSON
  import serialize from 'serialize-javascript';

  const html = `
    <script>
      window.__DATA__ = ${serialize(userData, { isJSON: true })};
    </script>
  `;
  ```

  ### Optimize bundle size

  Keep your server bundle separate from client bundle:

  ```javascript theme={null}
  // server-bundle.js - for Node.js
  import { renderToString } from 'preact-render-to-string';

  // client-bundle.js - for browsers
  import { hydrate } from 'preact';
  ```

  ### Handle errors gracefully

  ```javascript theme={null}
  import { renderToString } from 'preact-render-to-string';

  app.get('*', async (req, res) => {
    try {
      const html = renderToString(<App url={req.url} />);
      res.send(html);
    } catch (error) {
      console.error('SSR Error:', error);
      
      // Send fallback HTML that will render on client
      res.status(500).send(`
        <!DOCTYPE html>
        <html>
          <body>
            <div id="app"></div>
            <script src="/bundle.js"></script>
          </body>
        </html>
      `);
    }
  });
  ```
</Steps>

## Common Patterns

<CodeGroup>
  ```javascript Router Integration theme={null}
  import { renderToString } from 'preact-render-to-string';
  import { Router } from 'preact-router';
  import { App } from './App';

  app.get('*', (req, res) => {
    const html = renderToString(
      <Router url={req.url}>
        <App />
      </Router>
    );
    
    res.send(html);
  });
  ```

  ```javascript Static Site Generation theme={null}
  import { writeFileSync } from 'fs';
  import { renderToString } from 'preact-render-to-string';
  import { App } from './App';

  const routes = ['/', '/about', '/contact'];

  routes.forEach(route => {
    const html = renderToString(<App url={route} />);
    
    const page = `
      <!DOCTYPE html>
      <html>
        <body>
          <div id="app">${html}</div>
          <script src="/bundle.js"></script>
        </body>
      </html>
    `;
    
    writeFileSync(`dist${route}/index.html`, page);
  });
  ```

  ```javascript Async Component Data theme={null}
  import { renderToString } from 'preact-render-to-string';

  // Helper to wait for async data
  async function renderWithData(component) {
    const data = await fetchAllData();
    return renderToString(component(data));
  }

  app.get('/user/:id', async (req, res) => {
    const html = await renderWithData(
      (data) => <App user={data.user} />
    );
    
    res.send(html);
  });
  ```
</CodeGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="TypeScript" icon="code" href="/guides/typescript">
    Add type safety to your Preact application
  </Card>

  <Card title="Hooks" icon="hook" href="/guides/working-with-hooks">
    Master hooks for SSR-compatible components
  </Card>
</CardGroup>
