title: Upgrade Guide description: Upgrade TresJS to the latest version. navigation:
::warning This document covers the upgrade process from version 4.x to version 5.x. It's currently on nightly builds so you expect possible breaking changes. ::
To upgrade TresJS to the latest next release, run the following command:
:::code-group
pnpm add tresjs@next
npm install tresjs@next
yarn add tresjs@next
:::
When upgrading to a new major version of TresJS, you may need to make changes to your code. Below are the breaking changes introduced in v5 and how to migrate your code.
🚦 Impact Level: High
TresJS v5 is now ESM (ES Module) only. The UMD build configuration has been removed.
Update your import statements to use ESM syntax:
// ❌ Old CommonJS (no longer supported)
const { TresCanvas } = require('@tresjs/core')
// ✅ New ESM syntax
import { TresCanvas } from '@tresjs/core'
Update your build configuration to support ESM:
// package.json
{
"type": "module"
}
If using Node.js, ensure you're using Node.js 14+ and update your imports:
// ❌ Old
const Tres = require('@tresjs/core')
// ✅ New
import * as Tres from '@tresjs/core'
useLoader
Composable refactor🚦 Impact Level: High
The useLoader
composable has been completely refactored from a simple utility function wrapping Three.js loaders into a true Vue composable based on useAsyncData
. It now returns a reactive state with loading, error handling, and progress tracking. It previously wasn't a true composable but a utility wrapping Three.js loaders. 😬
useAsyncData
for better Vue integrationUpdate from promise-based to reactive state:
// ❌ Old v4 syntax - returned a promise
const gltf = await useLoader(GLTFLoader, '/models/duck.gltf')
// ✅ New v5 syntax - returns reactive state
const { state: gltf, isLoading, error } = useLoader(GLTFLoader, '/models/duck.gltf')
Handle loading states reactively:
<script setup>
// ✅ New v5 with reactive loading states and progress
const { state: model, isLoading, error, progress } = useLoader(
GLTFLoader,
'/models/duck.gltf'
)
// Watch for loading state changes
watch(isLoading, (loading) => {
if (loading) { console.log('Loading model...') }
})
// Watch for progress updates
watch(progress, (prog) => {
console.log(`Loading: ${prog.percentage}%`)
})
</script>
<template>
<primitive v-if="!isLoading && model?.scene" :object="model.scene" />
</template>
Add loader extensions (like DRACO):
// ✅ New v5 syntax with extensions
const { state: model, isLoading, progress } = useLoader(
GLTFLoader,
'/models/compressed.glb',
{
extensions: (loader) => {
if (loader instanceof GLTFLoader) {
loader.setDRACOLoader(dracoLoader)
}
}
}
)
Dynamic path loading:
// ✅ New v5 supports reactive paths
const modelPath = ref('/models/duck.gltf')
const { state: model, load } = useLoader(GLTFLoader, modelPath)
// Change path reactively
modelPath.value = '/models/fox.gltf' // Automatically reloads
// Or load programmatically
load('/models/another-model.gltf')
Texture loading example:
// ✅ New v5 texture loading
const { state: texture, isLoading } = useLoader(
TextureLoader,
'https://example.com/texture.jpg'
)
🚦 Impact Level: Moderate
The useTexture
composable has been completely removed from the core package and moved to @tresjs/cientos
.
Install @tresjs/cientos:
pnpm add @tresjs/cientos
Update your imports:
// ❌ Old v4 import
import { useTexture } from '@tresjs/core'
// ✅ New v5 import
import { useTexture } from '@tresjs/cientos'
Usage remains the same:
// ✅ Same usage pattern
const texture = await useTexture('/textures/brick.jpg')
🚦 Impact Level: Moderate
@pmndrs/pointer-events
package.useTresEventManager
composable has been removed.Update event handling expectations:
// ❌ Old behavior: multiple overlapping objects could trigger events
<TresMesh @click="handleClick">
<TresBoxGeometry />
<TresMeshBasicMaterial />
</TresMesh>
<TresMesh @click="handleClick"> <!-- This might not trigger if behind first mesh -->
<TresBoxGeometry />
<TresMeshBasicMaterial />
</TresMesh>
// ✅ New behavior: only first intersected object triggers event
// If you need multiple objects to handle events, ensure they don't overlap
// or handle the event at a parent level
Restructure overlapping interactive elements:
// ✅ Use a single parent handler for overlapping elements
<TresGroup @click="handleGroupClick">
<TresMesh>
<TresBoxGeometry />
<TresMeshBasicMaterial />
</TresMesh>
<TresMesh>
<TresBoxGeometry />
<TresMeshBasicMaterial />
</TresMesh>
</TresGroup>
Replace dash-case pointer events:
// ❌ Old behavior: dash-case events
<TresMesh @pointer-down="handlePointerDown">
// ✅ New behavior: native DOM event names
<TresMesh @pointerdown="handlePointerDown">
🚦 Impact Level: Low
Camera context is now a state object instead of the active camera instance.
Easy Migration Path:
For most use cases it is probably sufficient to change useTresContext
to useTres
:
// ❌ Old v4 syntax
const { camera } = useTresContext()
// ✅ Easy v5 migration
const { camera } = useTres()
// camera works the same as before
Advanced Usage:
If you need the full context (mainly for module authors), use useTresContext
:
// ✅ For module authors - full context access
const { camera } = useTresContext()
// camera is now an object with camera management methods
const activeCamera = camera.value.current
::read-more Learn more about the context system in our internal documentation. ::
🚦 Impact Level: Moderate
invalidate
, advance
, canBeInvalidated
and renderer instance now accessed through contextEasy Migration Path:
For most use cases it is probably sufficient to change useTresContext
to useTres
:
// ❌ Old v4 syntax
const { renderer, invalidate, advance } = useTresContext()
// ✅ Easy v5 migration
const { renderer, invalidate, advance } = useTres()
// Works the same as before for common use cases
Advanced Usage:
If you need the full context (mainly for module authors), use useTresContext
:
// ✅ For module authors - full context access
const { invalidate, advance, canBeInvalidated, renderer } = useTresContext()
// renderer is now readonly
// Use context methods for renderer operations
Performance monitoring changes:
// ❌ Old v4 performance access
const { performance } = useTresContext()
// ✅ New v5 - use renderer stats or custom performance monitoring
const { renderer } = useTres()
const stats = renderer.info
::read-more Learn more about the context system in our internal documentation. ::
🚦 Impact Level: Moderate
useTresReady
useSeek
useTresEventManager
useRaycaster
useRenderLoop
useLogger
useCamera
Replace useTresReady:
// ❌ Old v4 syntax
const { isReady } = useTresReady()
<script setup>
// ✅ New v5 alternative - use @ready event on TresCanvas
// Option 1: Template event (recommended for most users)
const onReady = (context) => {
console.log('Renderer is ready:', context.renderer.instance)
// Your ready logic here
}
</script>
<template>
<TresCanvas @ready="onReady">
<!-- Your 3D scene -->
</TresCanvas>
</template>
// Option 2: Composable approach (advanced users)
const { renderer } = useTresContext()
renderer.onReady((rendererInstance) => {
console.log('Renderer ready:', rendererInstance)
// Your ready logic here
})
Replace useSeek (if you were using it):
// ❌ Old v4 syntax
const { seek, seekByName, seekAll, seekAllByName } = useSeek()
const body = seek(car, 'name', 'Octane_Octane_Body_0')
const bones = seekAll(character, 'type', 'Bone')
// ✅ New v5 alternative - use useGraph or manual traversal
import { useGraph } from '@tresjs/core'
// Option 1: Use useGraph for structured access
const { state: model } = useLoader(GLTFLoader, '/path/to/model.glb')
const scene = computed(() => model.value?.scene)
const { nodes, materials } = useGraph(scene)
// Access objects by name directly
const body = computed(() => nodes.value?.Octane_Octane_Body_0)
// Option 2: Manual traversal function
function seek(object, property, value) {
if (!object) return null
if (object[property] === value) return object
for (const child of object.children) {
const found = seek(child, property, value)
if (found) return found
}
return null
}
// Usage
const body = seek(car.value, 'name', 'Octane_Octane_Body_0')
Replace useRenderLoop:
// ❌ Old v4 syntax
const { onLoop } = useRenderLoop()
onLoop(({ delta, elapsedTime }) => {
// Your loop logic here
})
// ✅ New v5 alternative - use context methods
const { onBeforeRender } = useLoop()
onBeforeRender(({ delta, elapsedTime }) => {
// Your loop logic here
})
::warning
useLoop
can only be used in child components of a TresCanvas
component, as its data is provided by TresCanvas
.
::
::read-more{TO="/api/composables/use-loop"}
Learn more about the useLoop
composable.
::
If the composable was used on a SFC (single file component), you can use the loop
event on the TresCanvas
component:
<script setup>
onLoop(({ delta, elapsedTime }) => {
// Your loop logic here
})
</script>
<template>
<TresCanvas @loop="onLoop">
<!-- Your 3D scene -->
</TresCanvas>
</template>
Replace useCamera:
// ❌ Old v4 syntax
const { camera } = useCamera()
// ✅ New v5 alternative - use context methods
const { camera } = useTres()
🚦 Impact Level: Low to Moderate
Several TresCanvas props are no longer reactive and are marked as @readonly
. These props were used to initialize the WebGL context and cannot be changed after the renderer is created without recreating the entire renderer and replacing the canvas element.
Props that lost reactivity:
alpha
- WebGL context alpha buffer settingdepth
- Depth buffer configurationstencil
- Stencil buffer configurationantialias
- Anti-aliasing settinglogarithmicDepthBuffer
- Logarithmic depth buffer settingpreserveDrawingBuffer
- Drawing buffer preservationpowerPreference
- GPU power preference (default
, high-performance
, low-power
)failIfMajorPerformanceCaveat
- Performance caveat handlingProps that remain reactive:
shadows
- Shadow renderingclearColor
- Background clear colorclearAlpha
- Clear color opacitytoneMapping
- Tone mapping techniqueshadowMapType
- Shadow map typetoneMappingExposure
- Tone mapping exposureoutputColorSpace
- Output color spaceuseLegacyLights
- Legacy lights systemrenderMode
- Render mode settingdpr
- Device pixel ratioWebGL context initialization parameters must be set when the WebGL context is created. These settings are passed directly to the WebGL renderer constructor and cannot be modified without recreating the entire renderer, which would be expensive and disruptive.
Set context initialization props as static values:
<script setup>
// ✅ These props should be set once and not changed
const rendererSettings = {
antialias: true,
alpha: false,
powerPreference: 'high-performance'
}
</script>
<template>
<!-- ✅ Use static values for WebGL context props -->
<TresCanvas
:antialias="rendererSettings.antialias"
:alpha="rendererSettings.alpha"
:power-preference="rendererSettings.powerPreference"
>
<!-- Your scene -->
</TresCanvas>
</template>
For conditional rendering based on device capabilities:
<script setup>
// ✅ Determine settings once based on device capabilities
const isHighPerformanceDevice = () => {
// Your device detection logic
return navigator.hardwareConcurrency > 4
}
const contextSettings = {
antialias: isHighPerformanceDevice(),
powerPreference: isHighPerformanceDevice() ? 'high-performance' : 'default'
}
</script>
<template>
<TresCanvas v-bind="contextSettings">
<!-- Your scene -->
</TresCanvas>
</template>
Update dynamic renderer options:
<script setup>
// ✅ These props can still be changed dynamically
const shadowsEnabled = ref(true)
const currentToneMapping = ref(ACESFilmicToneMapping)
const toggleShadows = () => {
shadowsEnabled.value = !shadowsEnabled.value
}
</script>
<template>
<TresCanvas
:shadows="shadowsEnabled"
:tone-mapping="currentToneMapping"
>
<!-- Your scene -->
</TresCanvas>
</template>
These breaking changes represent a major architectural improvement in TresJS v5, focusing on:
Take your time migrating and test thoroughly. The new APIs provide better developer experience and performance once migrated.