Using packs in Next.js
Serve downloaded GLBs from your Next.js app, render R3F on the client, and self-host decoders required by the selected files.
On this page (6)
Next.js can serve downloaded GLBs from public/ without a custom file loader. Render React Three Fiber in a Client Component, and configure only the decoders required by the selected files. Components are pack-specific and optional; you can always load a GLB directly with useGLTF.
Place the files#
A file under public/models/ is available from /models/ in the browser:
public/
models/
asset.glb
draco/ optional; required by Draco files
app/
scene/
Scene.tsx Client Component
SceneClient.tsx client-only dynamic wrapper
page.tsx Server ComponentIf you use an included component, preserve the URL it references or edit its useGLTF and preload paths to match your layout.
Render on the client#
'use client'
import { Canvas } from '@react-three/fiber'
import { Clone, OrbitControls, useGLTF } from '@react-three/drei'
function Model() {
const { scene } = useGLTF('/models/asset.glb')
return <Clone object={scene} />
}
export function Scene() {
return (
<Canvas camera={{ position: [4, 3, 6] }}>
<ambientLight intensity={0.6} />
<directionalLight position={[5, 8, 3]} />
<Model />
<OrbitControls />
</Canvas>
)
}
useGLTF.preload('/models/asset.glb')Current low-poly individual bundle files commonly need no decoder. For a compressed catalog or prebuild, self-host compatible Draco files under public/draco/ and pass '/draco/' as the second useGLTF argument in both the load and preload calls.
Fix a TS2503 from an included component#
On React 19, an older included component can fail to compile with TS2503: Cannot find namespace 'JSX'. React 19's @types/react removed the global JSX namespace that those components' prop type referenced.
Change the component's prop type to the version-agnostic form, which compiles on React 18 with R3F v8 and on React 19 with R3F v9:
export function Asset01(props: Omit<React.ComponentProps<typeof Clone>, 'object'>) {Nothing else in the component changes. Newer bundles already ship this form. See using a pack in React Three Fiber for the full comparison.
Keep WebGL out of server rendering#
Use a Client Component wrapper for a dynamic import with ssr: false:
'use client'
import dynamic from 'next/dynamic'
const Scene = dynamic(() => import('./Scene').then((module) => module.Scene), {
ssr: false,
loading: () => <p>Loading scene…</p>,
})
export function SceneClient() {
return <Scene />
}Your Server Component can render <SceneClient /> without evaluating WebGL in the server environment.
Checklist#
- Add
'use client'to files that render R3F or use browser APIs. - Serve GLBs by root-relative URL from
public/; do not import them as JavaScript modules. - Keep the component's load and preload URLs identical.
- Self-host Draco only for files that use Draco.
- Add KTX2 support only when the selected GLB contains KTX2 textures.
- Verify asset caching headers in your own hosting setup when you replace a file at the same URL.
threejsassets supplies downloadable files, not a hosted asset, decoder, or npm delivery service.
Related#
- Using a pack in React Three Fiber — load files and optional components.
- Using a pack in vanilla three.js — configure loaders directly.
- Setting up the Draco + KTX2 decoders — self-host decoder files.
Ask us directly — we answer support mail ourselves.