Skip to main content
Skip table of contents

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:

JS
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:

  1. Open the ShapeDiver Platform

  2. Navigate to your model's settings

  3. 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:

JS
// 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:

JS
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 ticket or modelViewUrl is 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 modelViewUrl is 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:

JS
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:

  1. Canvas element not in the DOM yet. Ensure the <canvas> exists before calling createViewport(). In React, initialize inside useEffect after the component mounts.

  1. Canvas has zero dimensions. A canvas with width: 0 or height: 0 produces no visible output. Ensure the canvas or its container has explicit dimensions:

HTML
<canvas id="canvas" style="width: 100%; height: 500px;"></canvas>
  1. WebGL not available. Some environments (headless browsers, certain VMs, server-side rendering) do not support WebGL. The viewer requires a WebGL 2-capable browser.

  1. 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:

JS
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:

JS
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:

  1. Wrong value format. Each parameter type expects a specific format. For example, StringList parameters take a string index ("0", "1"), not the display label. Use parameter.choices to inspect available options.

  1. Value out of range. The value may be outside the parameter's min/max bounds. Validate with:

JS
if (!parameter.isValid(newValue, true)) {
  console.warn("Invalid value:", newValue);
}
  1. customize() not called. Setting parameter.value alone does not trigger a computation. You must call await session.customize() afterward.

  1. 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:

JS
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:

HTML
<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:

HTML
<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:

JS
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:

JS
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:

JS
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:

  1. 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.

  1. 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:

JS
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

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.