KloradDocs

Quickstart

Build a Next.js app that shows a building and a live occupancy sensor in 3D. About fifteen minutes.

You will build a small twin of an exhibition hall in Thessaloniki: the hall itself, a counter at its entrance that reports how many people are inside, and a 3D view you can click. The readings come from a fixture source, a stand in for a real sensor that you swap later without touching the rest of the code.

Before you start

You need Node.js 18 or newer and pnpm (or npm), and access to the Klorad packages: an npm account that Prieston Technologies has added to the @klorad organisation under your licence. Install explains how access works.

Sign in to npm

npm login
npm view @klorad/api version

If the second command prints a version, your account can install the packages. If it says the package is not found, your account has not been given access yet.

Create the app

Make a folder my-twin with this package.json:

my-twin/package.json
{
  "name": "my-twin",
  "private": true,
  "scripts": {
    "dev": "next dev -p 3100"
  },
  "dependencies": {
    "@klorad/api": "^0.2.0",
    "@klorad/engine-three": "^0.2.0",
    "@klorad/react": "^0.2.0",
    "next": "15.5.7",
    "react": "^19.2.1",
    "react-dom": "^19.2.1",
    "three": "^0.170.0"
  },
  "devDependencies": {
    "@types/node": "^20.11.30",
    "@types/react": "^19.0.0",
    "typescript": "^5.9.0"
  }
}

and a root layout at my-twin/app/layout.tsx:

my-twin/app/layout.tsx
import type { ReactNode } from "react";

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body style={{ margin: 0, fontFamily: "system-ui, sans-serif" }}>{children}</body>
    </html>
  );
}

Describe the twin

Create my-twin/app/twin.tsx. Read it top to bottom: the provider creates a scene anchored on Earth, each SceneObject declares something that exists, the Connector starts the data source, and ThreeView draws it all.

my-twin/app/twin.tsx
"use client";

import { useMemo, useState } from "react";
import { fixtureSource } from "@klorad/api/integration";
import { Connector, KloradProvider, SceneObject, useLatest } from "@klorad/react";
import { ThreeView } from "@klorad/engine-three/react";

function Occupancy() {
  const latest = useLatest("entrance", "occupancy");
  return <p>{latest ? `${latest.value} ${latest.unit} at the entrance` : "Waiting for the first reading"}</p>;
}

export default function Twin() {
  const [selected, setSelected] = useState<string | null>(null);
  const source = useMemo(
    () => fixtureSource({ id: "gateway", channels: [{ entity: "entrance-01", quantity: "occupancy", unit: "people", value: (t) => 20 + (t % 7) }] }),
    [],
  );
  return (
    <KloradProvider options={{ coordinateSystem: { origin: { lat: 40.62637, lon: 22.94838 } } }}>
      <SceneObject id="hall" name="Exhibition hall" position={{ east: 0, north: 0 }} geometry={{ kind: "box", width: 24, depth: 14, height: 7 }} />
      <SceneObject
        id="entrance"
        name="Entrance counter"
        position={{ east: 16, north: -10 }}
        geometry={{ kind: "cylinder", radius: 2, height: 4 }}
        correspondence="shadow"
        binding={{ source: "gateway", entity: "entrance-01" }}
      />
      <Connector source={source} />
      <Occupancy />
      <div style={{ height: 480 }}>
        <ThreeView selected={selected} onPick={setSelected} />
      </div>
    </KloradProvider>
  );
}

Three things to notice:

  • Position is real. east: 16, north: -10 means sixteen metres east and ten south of the origin, which is a latitude and longitude. Every object is stored on Earth.
  • The entrance is a Digital Shadow. Its binding names a source and an entity, so readings from gateway about entrance-01 land on it and nowhere else.
  • useLatest re-renders on change. It reads the current reading, ordered by when it was observed, and subscribes for you.

Show it on a page

Create my-twin/app/page.tsx:

my-twin/app/page.tsx
import Twin from "./twin";

export default function Page() {
  return (
    <main style={{ padding: 24 }}>
      <h1>My first twin</h1>
      <Twin />
    </main>
  );
}

Run it

From the my-twin folder:

pnpm install
pnpm dev

Open http://localhost:3100: the hall and the counter appear in 3D, and the line above them counts people, updated every second. Click an object to select it.

What you built

A scene with one Digital Object (the hall, which nothing physical updates) and one Digital Shadow (the counter, fed one way by timestamped readings). That split is the heart of Klorad: every object says how it corresponds to the physical world, and the types stop you from feeding readings to something that has no physical counterpart.

Run it liveSandbox: Digital ShadowTimestamped readings from a fixture source: late arrivals, duplicates and staleness handled by the Time Spectrum.

Next

Something wrong or unclear? Tell your contact at Prieston Technologies.