Documentation

SSR with SvelteKit / Svelte

Shaders uses WebGPU, which requires a browser environment. While the Svelte package is built to be SSR-aware, the underlying WebGPU renderer requires a browser context to initialize. This page covers how to handle this correctly in SvelteKit.

The default behavior

Shaders for Svelte is built to be SSR-safe by design. When the <Shader> component is server-rendered, it renders nothing — no canvas element, no error. The GPU initialization only happens client-side on mount.

In many cases, you don't need any special handling:

<script>
  import { Shader, LinearGradient } from 'shaders/svelte'
</script>

<Shader class="w-full h-64">
  <LinearGradient />
</Shader>

This works in SvelteKit out of the box. The component renders empty on the server and initializes on the client.

onMount for browser-only code

If you have component-level code that depends on the shader being initialized (for example, you need to access the canvas element), wrap it in onMount:

<script>
  import { onMount } from 'svelte'
  import { Shader, LinearGradient } from 'shaders/svelte'

  let shaderEl

  onMount(() => {
    // Safe to interact with the DOM and canvas here
    console.log(shaderEl)
  })
</script>

<Shader bind:this={shaderEl} class="w-full h-64">
  <LinearGradient />
</Shader>

Conditional rendering with browser

For more explicit control over what renders on the server vs. client, use SvelteKit's $app/environment:

<script>
  import { browser } from '$app/environment'
  import { Shader, LinearGradient } from 'shaders/svelte'
</script>

{#if browser}
  <Shader class="w-full h-64">
    <LinearGradient />
  </Shader>
{:else}
  <!-- Optional server-rendered placeholder -->
  <div class="w-full h-64 bg-gray-900 rounded" />
{/if}

This gives you explicit control over the placeholder shown during SSR, which can help avoid layout shift.

Lazy loading

To defer loading the Shaders bundle entirely until client-side:

<script>
  import { onMount } from 'svelte'

  let ShaderComponent
  let LinearGradientComponent

  onMount(async () => {
    const { Shader, LinearGradient } = await import('shaders/svelte')
    ShaderComponent = Shader
    LinearGradientComponent = LinearGradient
  })
</script>

{#if ShaderComponent}
  <svelte:component this={ShaderComponent} class="w-full h-64">
    <svelte:component this={LinearGradientComponent} />
  </svelte:component>
{/if}