8.8 KiB
Common Problems & Debugging
These are the real-world issues that only surface after integration. Read this before finishing any Spline implementation.
🚨 Critical Gotchas (Will Break Your Site)
1. Scroll Hijacking — Page Won't Scroll
What happens: After adding Spline, the whole page stops scrolling. Users are stuck.
Why: Spline's auto-generated vanilla JS exports inject overflow: hidden into <body> CSS by default. This is baked into their generated code.
Fix:
/* Add this to your CSS — overrides Spline's injection */
body {
overflow: auto !important;
}
Or in Play Settings (Spline editor → Export → Play Settings), disable "Page Scroll" before generating the URL. This removes the overflow rule from the output.
Also check: If using the Runtime API and you embedded the generated index.html files, open them and manually remove the overflow: hidden line from the <style> block.
2. White Box Behind the 3D Scene
What happens: Your dark/transparent website has a white rectangle where the Spline scene is.
Why: The background color is set to white by default in Spline's export settings.
Fix:
- In Spline editor → Export → Play Settings → toggle Hide Background ON
- Click Generate Draft or Promote to Production — the URL does NOT auto-update with new settings
- Copy the new URL
For the web component you can also override inline:
<spline-viewer url="..." background="transparent"></spline-viewer>
3. Spline Scene Intermittently Fails to Load
What happens: Page loads fine sometimes, blank or broken other times. Feels random.
Why: The prod.spline.design CDN occasionally has latency or drops requests. There's no built-in retry or error handling.
Fix — add a timeout fallback:
const TIMEOUT_MS = 8000;
const timeoutId = setTimeout(() => {
// Spline didn't load in time — show fallback
document.getElementById('spline-fallback').style.display = 'block';
document.querySelector('.spline-wrapper').style.display = 'none';
}, TIMEOUT_MS);
// If using Runtime API, clear the timeout on successful load:
spline.load(sceneUrl).then(() => {
clearTimeout(timeoutId);
});
Long-term fix: Download the .splinecode file and self-host it on your own CDN. This eliminates the third-party dependency entirely and also fixes CORS issues.
4. Scene Looks Fine on Mac, Lags on Everything Else
What happens: Buttery smooth on MacBook Pro or M-chip Mac. Completely broken — laggy, stuttering, sometimes crashing — on mid-range Windows laptops or Android phones.
Why: Spline uses WebGL which runs on the GPU. Apple Silicon Macs have exceptional GPU performance. Most Windows laptops and Android devices do not have dedicated GPUs.
Fix — detect capability before loading:
function shouldLoadSpline() {
const isMobile = window.innerWidth < 768;
const isLowEnd = navigator.hardwareConcurrency <= 2;
// Optional: test WebGL support
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
const noWebGL = !gl;
return !isMobile && !isLowEnd && !noWebGL;
}
if (shouldLoadSpline()) {
loadSplineScene();
} else {
showFallback();
}
5. Layout Shift — Page Jumps When Scene Loads
What happens: User sees the page layout, then everything shifts/jumps when the 3D scene loads in.
Why: The canvas has no reserved height before loading, so the browser doesn't know how much space to allocate. HTML renders → space collapses → scene loads → everything jumps. This tanks your CLS (Cumulative Layout Shift) Core Web Vitals score.
Fix — pre-allocate space:
spline-viewer, canvas.spline-canvas {
display: block;
width: 100%;
height: 100vh; /* or whatever your target height is */
contain: strict; /* tells browser to reserve this space */
}
For position: fixed backgrounds this is less of an issue, but for inline scenes it's critical.
6. Rotation Values Are Radians, Not Degrees
What happens: You try to rotate an object to 90 degrees. It barely moves or spins wildly.
Why: Spline's runtime API uses radians, not degrees. 90 degrees = Math.PI / 2. 180 degrees = Math.PI.
Fix:
// WRONG
obj.rotation.y = 90;
// CORRECT
obj.rotation.y = Math.PI / 2; // 90 degrees
obj.rotation.y = Math.PI; // 180 degrees
obj.rotation.y = Math.PI * 2; // 360 degrees (full rotation)
// Helper function to use if you prefer degrees:
const toRad = (deg) => deg * (Math.PI / 180);
obj.rotation.y = toRad(90);
7. 3D Scene Blocks Clicks on Buttons / Links
What happens: Buttons, CTAs, or nav links that overlap with the Spline scene don't respond to clicks.
Why: The Spline canvas sits on top and captures all pointer events.
Fix: Add pointer-events: none to the Spline wrapper if it's decorative (no interaction needed):
.spline-wrapper {
pointer-events: none; /* scene won't capture any clicks */
}
If you need BOTH mouse interaction on the scene AND clickable content on top:
.spline-wrapper {
pointer-events: all; /* scene gets mouse events */
}
.content-overlay {
position: relative;
z-index: 10;
pointer-events: all; /* content also gets mouse events */
}
Note: When both have pointer-events: all, the topmost element (by z-index) wins. Make sure your content div has a higher z-index than the Spline wrapper.
8. Spline Watermark Visible (Free Plan)
What happens: A small "Built with Spline" logo appears in the corner.
Options:
Option A — Upgrade to a Spline paid plan. Then in Export → Play Settings → toggle "Hide Spline Logo" ON.
Option B — CSS overlay (free plan workaround):
/* Hides the watermark via CSS — targets the shadow DOM */
spline-viewer::part(logo) {
display: none;
}
/* Fallback if the above doesn't work */
spline-viewer {
--spline-viewer-logo-display: none;
}
Note: CSS-based hiding may break with Spline updates. The paid plan is the reliable solution.
9. CORS Error When Loading Scene
What happens: Scene fails to load with a CORS error in the console.
Why: Browser security blocks cross-origin requests in some environments (especially localhost dev servers with certain configurations).
Fix — self-host the scene file:
- In Spline → Export → Code Export → click the download icon next to the URL
- Download the
.splinecodefile - Host it on your own server or CDN (same origin as your site)
- Update the URL in your embed code to point to your hosted version
10. Next.js Hydration Error
What happens: React hydration mismatch error in Next.js when the Spline component is included.
Why: Spline renders on the client only (it needs the browser's WebGL), but Next.js tries to render on the server too.
Fix:
import dynamic from 'next/dynamic';
// ssr: false tells Next.js not to render this on the server
const Spline = dynamic(() => import('@splinetool/react-spline/next'), {
ssr: false,
loading: () => <div style={{ background: '#0a0a0a', height: '100vh' }} />
});
11. Scene URL Not Reflecting Latest Changes
What happens: You updated the scene in the Spline editor, but the embed still shows the old version.
Why: The prod.spline.design URL is a snapshot. It does not auto-update when you make changes.
Fix: Every time you make changes in the Spline editor, you must:
- Go to Export → Code Export
- Click "Promote to Production" (or "Generate Draft" for a new draft URL)
- The existing prod URL will now serve the updated scene — no need to change the URL in your code
Quick Diagnostic Table
| Symptom | Most Likely Cause | Fix |
|---|---|---|
| Page won't scroll | overflow: hidden injected by Spline |
Add body { overflow: auto !important } or disable Page Scroll in Play Settings |
| White box behind scene | Background not hidden | Play Settings → Hide Background → regenerate URL |
| Loads sometimes, blank others | CDN flakiness | Add timeout fallback; consider self-hosting |
| Smooth on Mac, laggy elsewhere | GPU performance gap | Add hardware detection, skip on low-end |
| Page jumps on load | No reserved space (CLS) | Set explicit height on canvas/viewer element |
| Rotations look wrong | Degrees vs radians | Use Math.PI / 180 * degrees |
| Buttons not clickable | Canvas capturing pointer events | Add pointer-events: none to Spline wrapper |
| Watermark visible | Free plan | Upgrade or use CSS override |
| CORS error | Cross-origin loading | Self-host the .splinecode file |
| Hydration error (Next.js) | SSR conflict | Use dynamic(() => import(...), { ssr: false }) |
| Old scene still showing | Didn't promote to production | Click "Promote to Production" in Spline editor |