Skip to main content
If something is not working, find your symptom below.

Loading and rendering

The most common cause is a missing or incorrectly pathed GLTF file.Check:
  1. Confirm src/assets/3d/bedroom.glb exists.
  2. Open the browser DevTools Network tab and look for a failed request to bedroom.glb (status 404).
  3. Check the Console tab for errors from useGLTF.
The model loads asynchronously via useGLTF(computerScene) where computerScene is imported from ../assets/3d/bedroom.glb. Until it resolves, the <Loader> component is shown. If the file is missing, the loader will hang indefinitely without an error message in the UI.Fix: Ensure the .glb file is in src/assets/3d/ and that the import in Computer.jsx resolves correctly:
src/models/Computer.jsx
import computerScene from '../assets/3d/bedroom.glb';

const { nodes, materials } = useGLTF(computerScene);
This is usually a WebGL support issue.Check:
  1. Open get.webgl.org in the same browser to confirm WebGL is enabled.
  2. On some systems, hardware acceleration is disabled in browser settings. Go to browser Settings → System and enable hardware acceleration.
  3. Try a different browser: Chrome, Edge, and Firefox all support WebGL 2. Safari may have issues with certain WebGL extensions.
Check the console for errors like:
  • WebGL context lost — GPU driver crash; refresh the page.
  • THREE.WebGLRenderer: Error creating WebGL context — hardware acceleration is disabled.
The embedded site is blocking iframes at the HTTP header level.Diagnose:
curl -I https://your-portfolio-url.com | grep -i 'x-frame-options\|content-security-policy'
  • X-Frame-Options: DENY → the site refuses all embedding. You cannot override this from the 3D portfolio side.
  • X-Frame-Options: SAMEORIGIN → the site only allows embedding from the same origin. The 3D portfolio on a different domain will be blocked.
  • No header, or Content-Security-Policy: frame-ancestors * → embedding is allowed.
Fix: Use a portfolio URL that does not set a restrictive framing header. Vercel-hosted React/Next.js apps do not add X-Frame-Options by default. If you control the embedded site, remove or relax the header.

Scene behavior

The ChangingTimeOverlay is shown for exactly 2 000 ms when the environment switches, then hidden by resetting the changingTime state.If the overlay persists indefinitely:
  1. Open DevTools and check for JavaScript errors that may have stopped the setTimeout callback.
  2. Confirm the useEffect in Home.jsx that resets changingTime is working correctly:
src/pages/Home.jsx
useEffect(() => {
  if (changingTime) {
    const timeoutId = setTimeout(() => setChangingTime(false), 2000);
    return () => clearTimeout(timeoutId);
  }
}, [changingTime]);
If the preset name passed to <Environment preset={...} /> is not a valid drei preset string, Three.js may throw and leave the component in a broken state. Valid values are:
apartment | city | dawn | forest | lobby | night | park | studio | sunset | warehouse
The bedroom GLTF is a high-polygon scene with baked textures, which is GPU-intensive.Things to try:
  • Close other browser tabs, especially ones with video or other WebGL content.
  • Use a device with a dedicated GPU rather than integrated graphics.
  • Reduce your browser zoom level (lower zoom = fewer pixels to render).
  • Close browser DevTools — the performance panel adds CPU overhead.
  • On laptops, plug in to power; many laptops throttle the GPU on battery.
If you are the developer and want to ship a lighter build, consider:
  • Re-exporting the GLTF from Blender with Draco compression enabled.
  • Using gltf-transform to compress textures to KTX2/WebP format.
This is intentional. The prompt text color switches based on the active environment preset:
  • night preset → white text (dark background)
  • All other presets → black text (lighter background)
If you add a custom environment preset or change the overlay background and the text becomes unreadable, adjust the conditional color logic in src/pages/Home.jsx.

Layout and mobile

The bottom details container uses env(safe-area-inset-bottom) padding to account for iPhone home indicator and Android gesture navigation bar.Check:
  1. Confirm the viewport meta tag is present in index.html:
index.html
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
If content is still being cut off on notched devices, you can add viewport-fit=cover to the viewport meta tag to enable env(safe-area-inset-bottom) support on iOS:
index.html
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
  1. If the layout still breaks on a specific device, check whether window.innerWidth is correctly reported. The ≤768px check in Loader.jsx switches between loader.jpg and loader-mobile.jpg.

Installation and build

The project requires Node.js 18 or later.
node --version
# Should print v18.x.x or higher
If you see peer dependency conflicts (common when mixing packages that declare overlapping React version requirements), add the --legacy-peer-deps flag:
npm install --legacy-peer-deps
--legacy-peer-deps instructs npm to use npm 6-style peer dependency resolution, which is more permissive. It is safe to use here because all runtime packages in this project are compatible with React 18.
Vite’s build will fail if any statically imported asset does not exist at the referenced path. The assets most likely to be missing after cloning or modifying the project are:
ImportExpected path
bedroom.glbsrc/assets/3d/bedroom.glb
retro.ttfsrc/assets/fonts/retro.ttf
loader.jpgsrc/assets/images/loader.jpg
loader-mobile.jpgsrc/assets/images/loader-mobile.jpg
Fix: Ensure all four files exist at the paths above. They are binary/large files that may not be included in a freshly cloned repository if they were added to .gitignore.To verify before building:
ls src/assets/3d/
ls src/assets/fonts/
ls src/assets/images/

Build docs developers (and LLMs) love