Skip to content
Guide

Three.js scenes in SvelteKit

Three.js needs the DOM, so initialize it only on the client. Keep the renderer scoped to a single container, update the camera on resize, and dispose of GPU resources when the route unmounts.

Best practices

  • Create the renderer and scene inside onMount to avoid SSR mismatch errors.
  • Cap devicePixelRatio to keep GPU load steady on high-DPI screens.
  • Use ResizeObserver for responsive resizing instead of window resize only.
  • Call dispose() on geometry and materials, then renderer.dispose().
  • Remove the canvas node from the DOM when you destroy the scene.
  • Pause animation loops when the page is hidden to save cycles.

SvelteKit-friendly setup

<script lang="ts">
	import { onDestroy, onMount } from "svelte";
	import * as THREE from "three";

	let containerEl;
	let renderer;
	let camera;
	let scene;
	let mesh;
	let resizeObserver;
	let frame = 0;

	const resize = () => {
		const { width, height } = containerEl.getBoundingClientRect();
		renderer.setSize(width, height, false);
		camera.aspect = width / height;
		camera.updateProjectionMatrix();
	};

	const render = (time) => {
		mesh.rotation.y = time * 0.0006;
		renderer.render(scene, camera);
		frame = requestAnimationFrame(render);
	};

	onMount(() => {
		scene = new THREE.Scene();
		camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
		camera.position.set(2, 1.5, 3);

		renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
		renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
		containerEl.appendChild(renderer.domElement);

		mesh = new THREE.Mesh(
			new THREE.BoxGeometry(1, 1, 1),
			new THREE.MeshStandardMaterial({ color: 0x38b884 })
		);
		scene.add(mesh);
		scene.add(new THREE.AmbientLight(0xffffff, 0.7));

		resize();
		resizeObserver = new ResizeObserver(resize);
		resizeObserver.observe(containerEl);
		frame = requestAnimationFrame(render);
	});

	onDestroy(() => {
		cancelAnimationFrame(frame);
		resizeObserver?.disconnect();
		mesh?.geometry.dispose();
		mesh?.material.dispose();
		renderer?.dispose();
	});
</script>

Checklist

  • Scene, camera, and renderer live inside the Svelte component.
  • renderer.setSize() uses the container width and height.
  • Cleanup detaches DOM and disposes GPU resources.
Built as a personal SvelteKit 5 lab with Supabase auth. Guides, patterns, and a playground you can actually ship.
Command Palette
Search for a command to run