Skip to Content
Framework SetupNext.js App Router

Next.js App Router

This setup targets React Immersive 5.2.0, React 19.2.4, and Next.js 16.2.7. Use Node 22.12+ for the included starter.

Install

In a Next.js App Router TypeScript application:

npm install next@16.2.7 react@19.2.4 react-dom@19.2.4 @liveroom-tech/react-immersive@5.2.0 three@^0.184.0 @react-three/fiber@^9.6.1 npm install -D typescript@^5.9.0 @types/node@^22 @types/react@~19.2.0 @types/react-dom@~19.2.0 @types/three@^0.184.0

Download red_room.glb  to public/red_room.glb. It is Red Room by LaisLH , licensed under CC BY 4.0 .

Add these files

For a new standalone app, explicitly set its root so Next.js does not inherit the surrounding documentation repository’s configuration or lockfile. If your application already has a Next.js config, merge these options into that file.

next.config.mjs
import { fileURLToPath } from "node:url"; const root = fileURLToPath(new URL(".", import.meta.url)); export default { turbopack: { root }, outputFileTracingRoot: root, };

Import component CSS from the root layout. This layout remains a Server Component.

app/layout.tsx
import type { ReactNode } from "react"; import "@liveroom-tech/react-immersive/simple-model-viewer.css"; export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body style={{ margin: 0 }}>{children}</body> </html> ); }

Put browser state and callbacks behind a client boundary. The dynamic import keeps the WebGL viewer out of server rendering; use it inside the client file.

app/Viewer.tsx
"use client"; import dynamic from "next/dynamic"; import { useState } from "react"; const SimpleModelViewer = dynamic( () => import("@liveroom-tech/react-immersive/simple-model-viewer").then((module) => module.SimpleModelViewer), { ssr: false, loading: () => <p>Starting viewer…</p> }, ); export default function Viewer() { const [status, setStatus] = useState("Loading model…"); return ( <main> <p role="status">{status}</p> <div style={{ height: "80vh" }}> <SimpleModelViewer modelUrl="/red_room.glb" onModelLoaded={(model) => setStatus(`Loaded ${model.objectCount} objects`)} onLoadError={() => setStatus("Model failed to load. Check the URL and browser console.")} /> </div> <p>Red Room by LaisLH, CC BY 4.0 — https://skfb.ly/6VBJR</p> </main> ); }
app/page.tsx
import Viewer from "./Viewer"; export default function Page() { return <Viewer />; }
npm run dev npm run build

Expect a visible room, scene objects, and the loaded-object count. The standalone Next.js starter  also includes package.json, TypeScript configuration and the sample asset. When nesting the app in another repository, scope compilerOptions.types to node, react, and react-dom, as the starter does, so unrelated ambient types from the parent repository are not loaded into the React 19 app.

Use licensed components

Create a key in the console , then set NEXT_PUBLIC_LICENSE_KEY=your-license-key in an uncommitted .env.local and restart Next.js. Use process.env.NEXT_PUBLIC_LICENSE_KEY in client code. Import the matching component stylesheet from app/layout.tsx and put hooks and event handlers in a file beginning with "use client".

NEXT_PUBLIC_ values are exposed in browser bundles. Domain-bound viewer keys are passed to the browser for validation; do not put account credentials, payment tokens, or server API secrets in these variables. See Licensing  and Getting Started .