Skip to main content
This page lives in the Developers section and is also referenced from Deployment. If you arrived from there, your sidebar has switched to Developers.

Frontend Architecture

Next.js 15, React 19, three specialized viewers, state management via Context, and i18n support.

Stack & Tooling

LayerTechnologyPurpose
FrameworkNext.js 15Server-side rendering, API routes, app router
UI RuntimeReact 19Component model, hooks, server components
Build ToolTurbopackFast incremental development builds
StylingTailwind CSSUtility-first CSS framework
ComponentsRadix UIHeadless, accessible component primitives
i18nnext-intlMulti-language support with routing

Visualization Engines

CDT ships three specialized WebGL/canvas-based viewers, each optimized for distinct data types:

MapLibre GL (maplibre-gl)

Renders 2D and 3D maps with vector tiles, basemaps, and satellite layers.

  • Data source: Martin tile server (PostGIS → Mapbox Vector Tiles)
  • Measurement: maplibre-gl-measures plugin for distance/area calculations
  • Geospatial ops: Turf.js for buffering, intersection, and analysis
  • Interactivity: Click to select building footprints, load details panel
  • Export: Share current viewport as URL + QR code

That Open Company BIM Viewer (@thatopen/components)

WebGL-based viewer for IFC (Industry Foundation Classes) building models.

  • Parser: web-ifc streams IFC geometry directly into browser memory
  • Streaming: Fragment-based loading allows large models (100MB+) to load progressively
  • Selection: Click elements to view properties and attach comments
  • Measurement: Built-in measurement tools
  • Export: BCF (BIM Collaboration Format) for round-trip with Revit, Archicad

Potree (potree-cdt)

Point cloud renderer for LiDAR scans and photogrammetry datasets.

  • Structure: Octree LOD (level-of-detail) — only visible geometry is loaded
  • Performance: Handles billions of points with flythrough smoothness
  • Styling: Height-based or intensity-based coloring
  • Export: Crop and download clips as LAS/LAZ

State Management

Application state is organized as a composition of React Context providers, each owning a focused domain slice. Rather than nesting providers manually, CDT uses a small compose utility (defined in @collabdt/core/store/CombineProviders.tsx) to flatten them into a single AppProvider:

// @collabdt/core/store/CombineProviders.tsx

const compose = providers =>
providers.reduce(
(Prev, Curr) =>
function ComposedProvider({ children }) {
return <Prev><Curr>{children}</Curr></Prev>
}
)

export const AppProvider = compose([
AppConfigProvider,
BimProvider,
MapProvider,
MenusProvider,
ToolsProvider,
ContentProvider,
DatasetsProvider,
FilesProvider,
BuildingsProvider,
PointCloudProvider,
PermissionsProvider,
])

AppProvider is rendered once at the application root and gives every component access to every context without prop drilling. See State Management for the full pattern (state, actions, reducers, and consumption).

Provider Reference

ProviderState OwnedAccess Pattern
AppConfigProviderOrganization settings, feature flags, map defaultsuseAppConfig()
PermissionsProviderCurrent user, role, CASL abilityusePermissions()
MapProviderMapLibre instance, active layers, viewportuseMap()
BimProviderIFC viewer engine, loaded fragments, selectionuseBim()
PointCloudProviderPotree viewer, loaded scenesusePointCloud()
MenusProviderSidebar/toolbar menu open/closed stateuseMenus()
ToolsProviderActive tool per viewer (measure, annotate, etc.)useTools()
ContentProviderWhich panel is shown, current view modeuseContent()
DatasetsProviderAvailable datasets, active layers, metadatauseDatasets()
FilesProviderUpload queue, progress, error stateuseFiles()
BuildingsProviderSelected building, cached propertiesuseBuildings()
CommentsProviderThreads, annotations, real-time updatesuseComments()

Data Fetching

Client-Side: SWR

Fetches use SWR (stale-while-revalidate) for smart caching and background revalidation:

// src/hooks/api.ts - thin wrapper around SWR

import useSWR from 'swr';

const fetcher = (url: string) => fetch(`/api${url}`).then(r => r.json());

export function useBuildings() {
const { data, error, isLoading } = useSWR('/buildings', fetcher);
return { buildings: data, error, isLoading };
}

export function useBuilding(id: string) {
const { data, error } = useSWR(id ? `/buildings/${id}` : null, fetcher);
return { building: data, error };
}

All API calls normalize through /api — the HTTP adapter masks server/client boundaries.

Real-Time: WebSocket (Future)

Currently synchronized via polling; real-time collaboration (shared cursors, live annotations) will use WebSocket/Pusher for sub-second updates.

UI Patterns

Viewer Layout

All three viewers share the same chrome:

  • Left sidebar: Navigation, data browser (buildings, sites, files)
  • Main viewport: Viewer canvas (map, BIM, or point cloud)
  • Right panel: Details/properties, comments, measurements
  • Bottom toolbar: Tools, datasets toggle, share, settings, language

Component Structure

Internationalization

See Internationalization for full i18n architecture. In short:

  • Message files: messages/{locale}.json (en, fr, es)
  • Component usage: const t = useTranslations()<h1>{t('page.title')}</h1>
  • Routing: /en/*, /fr/*, /es/* (or domain-based)
  • Format hooks: useFormatter() for numbers, dates, currency with locale awareness

Common Gotchas

  • Viewer refs: Viewers (MapLibre, Potree, That Open Company) maintain internal mutation state. Store refs in useRef, not state.
  • State sync: Map viewport + building selection must stay in sync across panels. Use useEffect dependencies carefully.
  • Memory: Large IFC/point cloud files load into browser memory. Monitor DevTools → Memory for leaks on viewer switches.
  • Permissions: UI controls hide via PermissionsProvider, but always verify on API side — frontend checks are for UX only.