Troubleshooting & Common Errors
This page covers common issues encountered when integrating the ShapeDiver Viewer, along with their solutions. If your issue is not listed here, please reach out on the forum.
Error Handling Basics
The Viewer provides a centralized error handling utility. Wrap your initialization and customization calls to catch and inspect errors:
import { processError } from "@shapediver/viewer";
try {
const session = await createSession({
ticket: "YOUR_TICKET",
modelViewUrl: "YOUR_MODEL_VIEW_URL",
});
} catch (e) {
const error = processError(e);
console.error(`[${error.code}] ${error.message}`);
}
processError() normalizes any thrown value into a structured error object with a code and message. This works for session creation, customization calls, and export requests.
See the Error Handling example for a complete demonstration.
Session & Connection Issues
HTTP 403 — Forbidden
Symptom: createSession() throws an error with HTTP status 403.
Cause: The domain your application is served from is not whitelisted in the model's embedding settings.
Solution:
Open the ShapeDiver Platform
Navigate to your model's settings
Under Embedding, add the domain your application runs on (e.g.,
https://myapp.com)
localhost is whitelisted by default during development. When deploying to production, you must add your production domain.
If you are using JWT authorization, a 403 can also indicate an expired or invalid token. See the JWT Token Issues section below.
HTTP 429 — Rate Limiting
Symptom: Customization requests fail intermittently with HTTP 429.
Cause: Too many requests sent in a short period. This typically happens when session.customize() is called on every input event (e.g., on every keystroke or slider movement).
Solution: Batch parameter changes into a single customize() call and debounce rapid user input:
// Set multiple parameters, then customize once
session.getParameterByName("Width")[0].value = 100;
session.getParameterByName("Height")[0].value = 50;
await session.customize(); // one request, not two
For sliders, debounce so that customize() is only called after the user stops moving:
let debounceTimer: ReturnType<typeof setTimeout>;
slider.addEventListener("input", () => {
clearTimeout(debounceTimer);
param.value = parseFloat(slider.value);
debounceTimer = setTimeout(() => session.customize(), 200);
});
Session Creation Hangs or Times Out
Symptom: createSession() never resolves.
Possible causes:
The
ticketormodelViewUrlis incorrect. Double-check both values from the Platform's embedding settings.A network firewall or proxy is blocking requests to
*.shapediver.com.The Geometry Backend system referenced by the
modelViewUrlis temporarily unavailable. Check the ShapeDiver status page.
JWT Token Issues
Symptom: Exports fail or return 403 after some time, even though the session was created successfully.
Cause: The JWT token has expired. Tokens have a limited lifetime.
Solution: Provide a refreshJwtToken callback so the viewer can automatically renew tokens:
session.refreshJwtToken = async () => {
const response = await fetch("/api/shapediver/refresh-token");
const { token } = await response.json();
return token;
};
The viewer calls this function automatically when a token-protected request fails due to expiration.
Viewport & Rendering Issues
Blank Canvas — Nothing Renders
Symptom: The canvas element is visible but remains blank (black or transparent).
Common causes and solutions:
Canvas element not in the DOM yet. Ensure the
<canvas>exists before callingcreateViewport(). In React, initialize insideuseEffectafter the component mounts.
Canvas has zero dimensions. A canvas with
width: 0orheight: 0produces no visible output. Ensure the canvas or its container has explicit dimensions:
<canvas id="canvas" style="width: 100%; height: 500px;"></canvas>
WebGL not available. Some environments (headless browsers, certain VMs, server-side rendering) do not support WebGL. The viewer requires a WebGL 2-capable browser.
Session not connected to viewport. A Viewport and Session are independent — creating both does not automatically link them. If the session's output is not visible, verify both were created successfully and that no errors were thrown.
WebGL Context Lost
Symptom: The viewer stops rendering and the console shows "WebGL context lost".
Cause: The browser reclaimed the GPU context, often due to too many active WebGL contexts on the page, GPU memory pressure, or the device going to sleep.
Solution: The viewer attempts to restore the context automatically. If your application creates multiple viewports, minimize the number of active ones. Close viewports that are not visible:
viewport.close();
Canvas Resizing — Viewer Doesn't Adapt
Symptom: After the browser window or container resizes, the viewer appears stretched or cropped.
Solution: Call viewport.resize() when the container dimensions change. For automatic handling, use a ResizeObserver:
const observer = new ResizeObserver(() => viewport.resize());
observer.observe(canvasContainer);
Parameter Issues
Parameter Change Has No Visible Effect
Symptom: Setting parameter.value and calling session.customize() completes without errors, but the 3D scene doesn't change.
Possible causes:
Wrong value format. Each parameter type expects a specific format. For example,
StringListparameters take a string index ("0","1"), not the display label. Useparameter.choicesto inspect available options.
Value out of range. The value may be outside the parameter's
min/maxbounds. Validate with:
if (!parameter.isValid(newValue, true)) {
console.warn("Invalid value:", newValue);
}
customize()not called. Settingparameter.valuealone does not trigger a computation. You must callawait session.customize()afterward.
The parameter does not affect visible geometry. Some parameters only affect exports or data outputs, not the 3D scene.
StringList Parameter — Value Doesn't Match
Symptom: Setting a StringList parameter to the label text (e.g., "Red") doesn't work.
Cause: StringList parameters use string indices as values, not labels.
Solution:
const param = session.getParameterByName("Color")[0];
// param.choices = ["Red", "Green", "Blue"]
// To select "Green", use the index:
param.value = "1"; // not "Green"
await session.customize();
CDN-Specific Issues
SDV is not defined
Symptom: Your script throws ReferenceError: SDV is not defined.
Cause: Your code runs before the CDN bundle has finished loading.
Solution: Ensure the viewer script has loaded before your code executes:
<script src="https://viewer.shapediver.com/v3/latest/sdv.bundle.js"></script>
<script>
// SDV is now available on the window object
const { createViewport, createSession } = SDV;
</script>
Or use the onload event:
<script
src="https://viewer.shapediver.com/v3/latest/sdv.bundle.js"
onload="initViewer()"
></script>
For feature modules (interactions, drawing tools, etc.), access them via window.SDVInteractions, window.SDVDrawingTools, etc.
CORS Errors with CDN
Symptom: Console shows CORS (Cross-Origin Resource Sharing) errors when loading the viewer bundle or assets.
Solution: If you are loading the CDN script from a different origin, ensure the script tag does not have conflicting crossorigin attributes. The default setup (no crossorigin attribute) works for most cases.
Framework-Specific Issues
React: "Viewport with this ID already exists"
Symptom: In development, the console shows an error about a viewport or session ID already being in use.
Cause: React 18 Strict Mode runs useEffect callbacks twice in development to help detect missing cleanup.
Solution: Return a cleanup function that closes the viewport and session:
useEffect(() => {
let viewport: IViewportApi | undefined;
let session: ISessionApi | undefined;
const init = async () => {
viewport = await createViewport({
canvas: canvasRef.current!,
id: "myViewport",
});
session = await createSession({
ticket: "...",
modelViewUrl: "...",
id: "mySession",
});
};
init();
return () => {
session?.close();
viewport?.close();
};
}, []);
React: Stale Closures in Event Handlers
Symptom: An event listener registered with addListener reads outdated state values.
Cause: The callback captures the state value at the time the effect ran. Subsequent state updates don't update the captured value.
Solution: Use a useRef to hold the current value, and read from the ref inside the callback:
const currentValueRef = useRef(someState);
currentValueRef.current = someState;
useEffect(() => {
const token = addListener(EVENTTYPE_INTERACTION.SELECT, (e) => {
// Read from ref, not from captured state
console.log(currentValueRef.current);
});
return () => removeListener(token);
}, []);
Next.js / SSR: window is not defined
Symptom: Build or server-side rendering fails with ReferenceError: window is not defined.
Cause: The Viewer requires a browser environment (DOM, WebGL). It cannot run during server-side rendering.
Solution: Use dynamic imports with SSR disabled:
import dynamic from "next/dynamic";
const ShapeDiverViewer = dynamic(
() => import("../components/ShapeDiverViewer"),
{ ssr: false }
);
Visual & Appearance Issues
Colors Are Different from Rhino
ShapeDiver uses a physically-based rendering (PBR) pipeline with linear color space, while Rhino uses a different rendering engine. Some color differences are expected due to:
Color space: The viewer uses linear color space internally. The Color Management page explains how the automatic color adjustment works and how to configure it.
Lighting: The default light setup in the viewer differs from Rhino's default. Adjusting the light scene can bring results closer.
Materials: Rhino materials don't always map 1:1 to PBR materials. Use the glTF 2.0 Display component in Grasshopper for the most accurate material control.
Model Appears Too Dark or Too Bright
Possible causes:
The environment map strongly influences lighting. Try different environment maps via
viewport.environmentMap.Shadow intensity may be too high. Adjust
viewport.shadowMapIntensity.If using custom materials via the API, ensure color values are in the correct space. See Color Management.
Export Issues
Export Returns Empty or Undefined Content
Symptom: exportApi.request() resolves but result.content is empty or undefined.
Possible causes:
The export depends on parameters that haven't been customized yet. Ensure you call
session.customize()before requesting the export if parameter values have changed.
The Grasshopper definition produces no output for the current parameter combination. This is a model-side issue — verify in the ShapeDiver Platform that the export produces output with the same parameter values.
Export Download Requires Authorization
Symptom: The download URL from result.content[0].href returns 403.
Cause: The model has JWT authorization enabled for exports.
Solution: Use the authorized download pattern:
const result = await exportApi.request();
const href = result.content?.[0].href;
if (href) {
// If JWT is configured, the href may require the token as a query parameter
// or use session.getExportDownloadUrl() if available
window.open(href);
}
See the Export Request Download With JwtToken example for the complete authorized download pattern.
Additional Resources
API Reference: Full technical documentation
Live Examples: Functional examples with source code
Community Forum: Ask questions and get help from the ShapeDiver team
Error Handling Example: How to catch and process viewer errors