or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

server.mddocs/reference/

svelte/server

The svelte/server module provides server-side rendering (SSR) capabilities for Svelte applications. This module is only available on the server and when compiling with the server option.

Functions

render()

Renders a Svelte component to HTML on the server.

function render<Comp, Props>(
  component: Comp,
  options?: {
    props?: Props;
    context?: Map<any, any>;
    idPrefix?: string;
    csp?: Csp;
  }
): RenderOutput

Parameters:

  • component: The Svelte component to render (either a Svelte 5 component function or a Svelte 4 class component)
  • options (optional): Configuration object
    • props: Component properties to pass during rendering
    • context: Context map that can be accessed via getContext() at the component level
    • idPrefix: Custom prefix for generated element IDs (useful for avoiding ID collisions when rendering multiple components)
    • csp: Content Security Policy configuration for inline scripts and styles

Returns: A RenderOutput object containing the rendered HTML and CSP hashes

Description:

The render() function performs server-side rendering of a Svelte component, generating HTML that can be sent to the client. The function returns a special object that can be used both synchronously and asynchronously, allowing you to handle components with or without async operations.

Usage Examples:

Basic SSR

import { render } from 'svelte/server';
import App from './App.svelte';

const result = render(App, {
  props: { name: 'World' }
});

console.log(result.body); // '<div>Hello World</div>'
console.log(result.head); // '<style>...</style>'

With Context

import { render } from 'svelte/server';
import App from './App.svelte';

const context = new Map();
context.set('theme', 'dark');
context.set('user', { id: 123, name: 'Alice' });

const result = render(App, {
  props: { title: 'Dashboard' },
  context
});

Async Rendering

import { render } from 'svelte/server';
import App from './App.svelte';

// If your component has async operations (data fetching, etc.),
// you can await the render result
const result = await render(App, {
  props: { userId: 123 }
});

const html = `
  <!DOCTYPE html>
  <html>
    <head>
      ${result.head}
    </head>
    <body>
      ${result.body}
    </body>
  </html>
`;

With Content Security Policy (Nonce)

import { render } from 'svelte/server';
import App from './App.svelte';
import crypto from 'crypto';

// Generate a nonce for this request
const nonce = crypto.randomBytes(16).toString('base64');

const result = render(App, {
  props: { data: myData },
  csp: { nonce }
});

const html = `
  <!DOCTYPE html>
  <html>
    <head>
      <meta http-equiv="Content-Security-Policy"
            content="script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'">
      ${result.head}
    </head>
    <body>
      ${result.body}
    </body>
  </html>
`;

With Content Security Policy (Hash)

import { render } from 'svelte/server';
import App from './App.svelte';

const result = await render(App, {
  props: { data: myData },
  csp: { hash: true }
});

// Generate CSP header with hashes
const scriptHashes = result.hashes.script.join(' ');
const cspHeader = `script-src ${scriptHashes}`;

// Send CSP header in HTTP response
response.setHeader('Content-Security-Policy', cspHeader);

Node.js HTTP Server Integration

import { createServer } from 'http';
import { render } from 'svelte/server';
import App from './App.svelte';

const server = createServer(async (req, res) => {
  try {
    const result = await render(App, {
      props: {
        path: req.url,
        query: new URLSearchParams(req.url.split('?')[1])
      }
    });

    res.writeHead(200, {
      'Content-Type': 'text/html',
      'Content-Length': Buffer.byteLength(result.body)
    });

    res.end(`
      <!DOCTYPE html>
      <html>
        <head>
          ${result.head}
        </head>
        <body>
          ${result.body}
          <script type="module" src="/app.js"></script>
        </body>
      </html>
    `);
  } catch (error) {
    res.writeHead(500);
    res.end('Internal Server Error');
  }
});

server.listen(3000);

Express Integration

import express from 'express';
import { render } from 'svelte/server';
import App from './App.svelte';

const app = express();

app.get('*', async (req, res) => {
  try {
    const result = await render(App, {
      props: {
        url: req.url,
        user: req.user
      }
    });

    res.send(`
      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width">
          ${result.head}
        </head>
        <body>
          <div id="app">${result.body}</div>
          <script type="module" src="/client.js"></script>
        </body>
      </html>
    `);
  } catch (error) {
    console.error('SSR error:', error);
    res.status(500).send('Server Error');
  }
});

app.listen(3000);

Client-Side Hydration

After rendering on the server, you need to hydrate the component on the client:

Server (server.js):

import { render } from 'svelte/server';
import App from './App.svelte';

export async function handleRequest(req, res) {
  const result = await render(App, {
    props: { initialData: await fetchData() }
  });

  res.send(`
    <!DOCTYPE html>
    <html>
      <head>${result.head}</head>
      <body>
        <div id="app">${result.body}</div>
        <script type="module" src="/client.js"></script>
      </body>
    </html>
  `);
}

Client (client.js):

import { hydrate } from 'svelte';
import App from './App.svelte';

// Hydrate the server-rendered HTML
hydrate(App, {
  target: document.getElementById('app'),
  props: { initialData: window.__INITIAL_DATA__ }
});

SvelteKit Integration

In SvelteKit, server-side rendering is handled automatically, but you can use render() for custom rendering scenarios:

// +page.server.js
import { render } from 'svelte/server';
import EmailTemplate from './EmailTemplate.svelte';

export async function POST({ request }) {
  const data = await request.json();

  // Render component to HTML string (e.g., for emails)
  const result = render(EmailTemplate, {
    props: {
      userName: data.userName,
      orderNumber: data.orderNumber
    }
  });

  // Send email with rendered HTML
  await sendEmail({
    to: data.email,
    subject: 'Order Confirmation',
    html: result.body
  });

  return new Response('Email sent');
}

Notes:

  • The render() function returns a special object that works both synchronously and as a Promise
  • If your component performs asynchronous operations (data fetching, awaiting promises), you should await the result
  • The returned object has head and body properties that contain the rendered HTML
  • The html property is deprecated; use body instead
  • Components are rendered in "server mode" where certain lifecycle functions (like onMount) do not execute
  • The function validates that nonce and hash are not both specified in CSP options

Types

RenderOutput

The result of server-side rendering a component.

interface RenderOutput extends PromiseLike<SyncRenderOutput> {
  head: string;
  body: string;
  html: string; // @deprecated use 'body' instead
  hashes: {
    script: Sha256Source[];
  };
}

Properties:

  • head: HTML content to inject into the <head> element (styles, meta tags, etc.)
  • body: HTML content representing the rendered component body
  • html: Deprecated alias for body; use body instead
  • hashes: Content Security Policy hashes for inline scripts
    • script: Array of SHA-256 hashes for inline scripts in the format 'sha256-...'

Description:

The RenderOutput type represents the result of calling render(). It's both a regular object with accessible properties and a Promise-like object that can be awaited for async rendering.

The object uses lazy evaluation: accessing head or body triggers synchronous rendering, while awaiting the object performs asynchronous rendering if the component has async operations.

Usage Examples:

Synchronous Access

import { render } from 'svelte/server';
import App from './App.svelte';

const result = render(App);

// Access properties directly
console.log(result.head);  // '<style>.svelte-xyz{...}</style>'
console.log(result.body);  // '<div class="svelte-xyz">...</div>'

Asynchronous Access

import { render } from 'svelte/server';
import AsyncApp from './AsyncApp.svelte';

// Await the result for components with async operations
const result = await render(AsyncApp, {
  props: { userId: 123 }
});

console.log(result.body);  // Fully resolved HTML with async data

Building Complete HTML Document

import { render } from 'svelte/server';
import App from './App.svelte';

const result = await render(App, { props: { title: 'My App' } });

const html = `
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    ${result.head}
  </head>
  <body>
    <div id="root">${result.body}</div>
    <script type="module" src="/bundle.js"></script>
  </body>
</html>
`;

Using CSP Hashes

import { render } from 'svelte/server';
import App from './App.svelte';

const result = await render(App, {
  csp: { hash: true }
});

// Build CSP header from hashes
const cspScripts = result.hashes.script.join(' ');
const cspHeader = `script-src 'self' ${cspScripts}`;

console.log(cspHeader);
// 'script-src 'self' sha256-abc123... sha256-def456...'

Handling Deprecated 'html' Property

import { render } from 'svelte/server';
import App from './App.svelte';

const result = render(App);

// ✅ Recommended: Use 'body'
console.log(result.body);

// ❌ Deprecated: Avoid 'html'
// console.log(result.html); // This still works but is deprecated

Notes:

  • The RenderOutput is both an object and Promise-like, allowing flexible usage patterns
  • Accessing head or body properties triggers synchronous rendering on first access
  • Awaiting the result ensures async operations complete before returning HTML
  • The hashes.script array is only populated when csp.hash is enabled
  • The html property is deprecated in favor of body for clearer semantics

Csp

Configuration options for Content Security Policy when rendering.

interface Csp {
  nonce?: string;
  hash?: boolean;
}

Properties:

  • nonce: A unique nonce value to add to inline scripts and styles (base64-encoded random string)
  • hash: Whether to generate SHA-256 hashes for inline scripts and styles

Description:

The Csp interface configures Content Security Policy (CSP) behavior during server-side rendering. CSP helps prevent cross-site scripting (XSS) attacks by controlling which scripts can execute on your page.

Svelte provides two CSP strategies:

  1. Nonce-based: Generate a unique nonce per request and add it to inline scripts/styles
  2. Hash-based: Generate SHA-256 hashes of inline content for CSP headers

Important: You cannot use both nonce and hash simultaneously; render() will throw an error if both are specified.

Usage Examples:

Nonce-Based CSP

The nonce strategy generates a unique token for each request and adds it to all inline scripts and styles:

import { render } from 'svelte/server';
import crypto from 'crypto';
import App from './App.svelte';

// Generate a cryptographically secure nonce
const nonce = crypto.randomBytes(16).toString('base64');

const result = render(App, {
  props: { data: myData },
  csp: { nonce }
});

// Build HTML with CSP meta tag
const html = `
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Security-Policy"
          content="script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'">
    ${result.head}
  </head>
  <body>
    ${result.body}
  </body>
</html>
`;

// Or set CSP via HTTP header
response.setHeader(
  'Content-Security-Policy',
  `script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'`
);

Hash-Based CSP

The hash strategy calculates SHA-256 hashes of inline content:

import { render } from 'svelte/server';
import App from './App.svelte';

const result = await render(App, {
  props: { data: myData },
  csp: { hash: true }
});

// Extract script hashes
const scriptHashes = result.hashes.script.join(' ');

// Build CSP header
const cspHeader = `script-src 'self' ${scriptHashes}; style-src 'self' 'unsafe-inline'`;

// Set CSP via HTTP header
response.setHeader('Content-Security-Policy', cspHeader);

Complete Nonce Example (Express)

import express from 'express';
import { render } from 'svelte/server';
import crypto from 'crypto';
import App from './App.svelte';

const app = express();

app.get('*', async (req, res) => {
  // Generate nonce for this request
  const nonce = crypto.randomBytes(16).toString('base64');

  const result = await render(App, {
    props: { url: req.url },
    csp: { nonce }
  });

  // Set CSP header
  res.setHeader(
    'Content-Security-Policy',
    `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'`
  );

  res.send(`
    <!DOCTYPE html>
    <html>
      <head>${result.head}</head>
      <body>${result.body}</body>
    </html>
  `);
});

Complete Hash Example (Node.js)

import { createServer } from 'http';
import { render } from 'svelte/server';
import App from './App.svelte';

const server = createServer(async (req, res) => {
  const result = await render(App, {
    csp: { hash: true }
  });

  // Build CSP with hashes
  const scriptSources = ['self', ...result.hashes.script];
  const csp = `script-src ${scriptSources.join(' ')}; object-src 'none'`;

  res.writeHead(200, {
    'Content-Type': 'text/html',
    'Content-Security-Policy': csp
  });

  res.end(`
    <!DOCTYPE html>
    <html>
      <head>${result.head}</head>
      <body>${result.body}</body>
    </html>
  `);
});

server.listen(3000);

SvelteKit Hooks Example

In SvelteKit, you can configure CSP in your hooks:

// src/hooks.server.js
import { dev } from '$app/environment';
import crypto from 'crypto';

export async function handle({ event, resolve }) {
  // Generate nonce for CSP
  const nonce = crypto.randomBytes(16).toString('base64');

  // Make nonce available to pages
  event.locals.nonce = nonce;

  const response = await resolve(event, {
    transformPageChunk: ({ html }) => {
      // Inject nonce into script tags if needed
      return html;
    }
  });

  // Set CSP header
  if (!dev) {
    response.headers.set(
      'Content-Security-Policy',
      `script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'`
    );
  }

  return response;
}

Choosing Between Nonce and Hash

Use Nonce when:

  • You generate HTML dynamically per request
  • Content changes frequently
  • You want simpler CSP headers
  • You have server-side rendering for each request

Use Hash when:

  • You cache rendered HTML
  • Content is static or changes infrequently
  • You want to avoid generating nonces
  • You serve pre-rendered content

Comparison:

// ❌ Invalid: Cannot use both
const result = render(App, {
  csp: {
    nonce: 'abc123',
    hash: true  // Error: Cannot specify both nonce and hash
  }
});

// ✅ Valid: Nonce only
const result1 = render(App, {
  csp: { nonce: 'abc123' }
});

// ✅ Valid: Hash only
const result2 = render(App, {
  csp: { hash: true }
});

// ✅ Valid: No CSP
const result3 = render(App, {
  csp: undefined
});

Security Best Practices:

  1. Generate secure nonces: Use cryptographically secure random values

    const nonce = crypto.randomBytes(16).toString('base64');
  2. Use strict CSP policies: Avoid 'unsafe-inline' and 'unsafe-eval'

    script-src 'self' 'nonce-xyz'; object-src 'none'
  3. Regenerate nonces per request: Never reuse nonces across requests

  4. Include all necessary directives: Configure script-src, style-src, and other directives

  5. Test your CSP: Monitor CSP violations in production

    Content-Security-Policy-Report-Only: ...; report-uri /csp-report

Notes:

  • Using both nonce and hash simultaneously throws an error
  • Nonces should be cryptographically random and unique per request
  • Hashes are deterministic based on content but require async rendering
  • CSP configuration is optional; omitting it allows all inline scripts
  • The Csp type currently only includes nonce and hash, but may be extended in future versions

Server-Side Rendering Overview

How SSR Works in Svelte

Server-side rendering in Svelte follows these steps:

  1. Component Compilation: Svelte compiler generates both client and server versions of your components
  2. Server Rendering: The render() function executes the component in a server environment
  3. HTML Generation: Component produces HTML strings with appropriate markers for hydration
  4. Client Hydration: Client-side code attaches event listeners and makes the app interactive

Lifecycle Differences

When rendering on the server, certain lifecycle behaviors differ from client-side:

Functions that DO run on server:

  • onDestroy - Cleanup operations
  • getContext / setContext - Context management
  • Component initialization code

Functions that DON'T run on server:

  • onMount - Browser-only lifecycle
  • beforeUpdate / afterUpdate - No DOM updates on server
  • tick() - Returns immediately without waiting
  • Browser APIs - window, document, etc. are not available

Example:

<script>
  import { onMount, onDestroy, getContext } from 'svelte';

  // ✅ Runs on both server and client
  const theme = getContext('theme');
  console.log('Component initializing');

  // ✅ Runs on server (cleanup)
  onDestroy(() => {
    console.log('Cleanup');
  });

  // ❌ Only runs on client
  onMount(() => {
    console.log('Mounted to DOM');
    return () => {
      console.log('Unmounting from DOM');
    };
  });

  // ✅ Runs on server, but needs checking
  let isServer = typeof window === 'undefined';

  if (!isServer) {
    // Client-only code
    window.addEventListener('resize', handleResize);
  }
</script>

Handling Async Data

Components can fetch data during SSR using async/await:

<script>
  // This will be fetched during SSR
  async function loadData() {
    const response = await fetch('/api/data');
    return response.json();
  }

  const data = $derived(await loadData());
</script>

{#await loadData()}
  <p>Loading...</p>
{:then data}
  <div>{data.title}</div>
{/await}

Server:

// Await the render to ensure data is fetched
const result = await render(App);

Streaming SSR

Svelte 5 supports streaming server-side rendering for better performance:

import { render } from 'svelte/server';
import { Readable } from 'stream';
import App from './App.svelte';

export async function handleRequest(req, res) {
  // Start rendering
  const result = render(App, { props: { url: req.url } });

  res.writeHead(200, { 'Content-Type': 'text/html' });

  // Stream the initial HTML
  res.write(`
    <!DOCTYPE html>
    <html>
      <head>${result.head}</head>
      <body>
  `);

  // Await async content
  const { body } = await result;
  res.write(body);

  res.end(`
      </body>
    </html>
  `);
}

Error Handling

Proper error handling is crucial for SSR:

import { render } from 'svelte/server';
import App from './App.svelte';
import ErrorPage from './ErrorPage.svelte';

export async function handleRequest(req, res) {
  try {
    const result = await render(App, {
      props: { url: req.url }
    });

    res.status(200).send(`
      <!DOCTYPE html>
      <html>
        <head>${result.head}</head>
        <body>${result.body}</body>
      </html>
    `);
  } catch (error) {
    console.error('SSR Error:', error);

    // Render error page
    const errorResult = render(ErrorPage, {
      props: {
        message: error.message,
        stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
      }
    });

    res.status(500).send(`
      <!DOCTYPE html>
      <html>
        <head>${errorResult.head}</head>
        <body>${errorResult.body}</body>
      </html>
    `);
  }
}

Environment Detection

Detect whether code is running on server or client:

export const isServer = typeof window === 'undefined';
export const isBrowser = typeof window !== 'undefined';

// In component
if (isServer) {
  // Server-only logic
} else {
  // Client-only logic
}

Best Practices

  1. Always await async renders: If your component fetches data, await the render() result

    const result = await render(App);
  2. Use context for server data: Pass server-side data through context

    const context = new Map();
    context.set('request', { url: req.url, headers: req.headers });
    const result = render(App, { context });
  3. Guard browser-only code: Check for browser environment before using browser APIs

    if (typeof window !== 'undefined') {
      window.addEventListener('scroll', handleScroll);
    }
  4. Implement proper CSP: Use nonce or hash-based CSP for security

    const result = render(App, { csp: { nonce: generateNonce() } });
  5. Handle errors gracefully: Always catch and handle rendering errors

    try {
      const result = await render(App);
    } catch (error) {
      // Render error page or fallback
    }
  6. Optimize for performance: Cache rendered content when possible

    const cache = new Map();
    const cacheKey = req.url;
    
    if (cache.has(cacheKey)) {
      return cache.get(cacheKey);
    }
    
    const result = await render(App);
    cache.set(cacheKey, result);
  7. Use proper hydration: Ensure client-side hydration matches server HTML

    // Client
    import { hydrate } from 'svelte';
    hydrate(App, { target: document.getElementById('app') });

See Also

  • Svelte Lifecycle Functions
  • Context API
  • SvelteKit SSR
  • Content Security Policy