Home / Guides / Host a Three.js scene
A WebGL scene on your machine is a demo; the same scene at a URL is something you can put in a portfolio, a client thread, or a tweet. Three.js scenes host anywhere static files do — the only trick is making the file self-contained. Here's the template, and the fix for the classic "works locally, blank everywhere else" trap.
Host your scene nowModern Three.js is ES-module first. The cleanest single-file setup uses an import map pointing at a CDN, so there's no bundler and no node_modules:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>html,body{margin:0;overflow:hidden}canvas{display:block}</style>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
}
}
</script>
</head>
<body>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, innerWidth/innerHeight, 0.1, 100);
camera.position.z = 3;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
new OrbitControls(camera, renderer.domElement);
// your scene here
const mesh = new THREE.Mesh(
new THREE.TorusKnotGeometry(0.8, 0.25, 128, 32),
new THREE.MeshNormalMaterial()
);
scene.add(mesh);
addEventListener("resize", () => {
camera.aspect = innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
renderer.setAnimationLoop(() => {
mesh.rotation.y += 0.005;
renderer.render(scene, camera);
});
</script>
</body>
</html>
Replace the torus knot with your scene. The import map means import * as THREE from "three" resolves in the browser with no build step, and the addons path gives you OrbitControls, loaders, and postprocessing the same way.
slug.htmldrop.app URL — the scene renders live, full-window, with working orbit controls, for anyone who opens it. Anonymous links last 7 days.If you've ever double-clicked a Three.js HTML file and gotten a black or empty page, you've met the file:// problem: browsers refuse module imports and asset fetches from local files for security reasons. The usual advice is "run a local server" — true, but that doesn't help the person you're sending it to. Hosting the file puts it on HTTPS where modules, import maps, and loaders just work, for you and for them. That's the whole reason to prefer a URL over an attachment for WebGL work: see sharing an HTML file for the general version of this argument.
Once your scene calls TextureLoader or GLTFLoader with local paths, single-file stops being enough. Make a free account and drop a folder or .zip: index.html at the root, assets/ beside it, relative paths untouched. Free accounts take 10 MB per drop — plenty for compressed GLB and KTX2 textures — and Pro ($16/mo) raises that to 250 MB when the art direction wins the file-size argument. Re-upload a new version and the URL stays stable, with version history on paid plans so a broken update is one rollback away.
Drops are unlisted by default — unguessable URLs, not indexed by search engines unless you opt in — which fits work-in-progress and client reviews. For finished pieces, opt the drop into indexing, or put it on your own domain (Plus, $10/mo) so the URL reads like your portfolio, not a host. Password protection (from $5/mo) covers the "only the client sees this" case. And on paid plans you can embed the scene in an iframe on your own site with a hostname allowlist.
If the scene came out of Claude, Cursor, or Cline, add the htmldrop MCP server (https://htmldrop.app/mcp — OAuth browser sign-in, no API keys) and say "make this self-contained with an import map and publish it". The assistant builds the wrapper and returns the live URL in one go. Details in publishing from Claude Code; the same flow covers p5.js sketches.
Free, anonymous, full-window WebGL. Your work at a link, nothing else around it.
Try it freeYes. Drops are served as normal static pages over HTTPS, so script type="module", import maps, and CDN imports of three all work exactly as they do on any web host. No bundler or build step is required.
Create a free account and drop a folder or .zip: index.html at the root, your assets beside it, and relative paths in TextureLoader or GLTFLoader work unchanged. Free accounts take 10 MB per drop; Pro raises that to 250 MB for heavy textures and models.
Opening an HTML file from disk uses the file:// scheme, where browsers block module imports and asset fetches for security. That's the classic blank-canvas cause. Hosting the same file on htmldrop serves it over HTTPS, which makes modules, import maps, and loaders behave normally.
Yes — add htmldrop's hosted MCP server (https://htmldrop.app/mcp, OAuth browser sign-in, no API keys) to Claude Code, Claude Desktop, Cursor, or Cline and ask it to publish the scene; it returns the live URL in one tool call.