Context
The site's content is structured as a knowledge graph with ~150 nodes
(projects, blog posts, ADRs, technologies, roles, tags) and hundreds of edges representing relationships like
USES_TECHNOLOGY, PART_OF_PROJECT, CREATED_AT_ROLE, and HAS_TAG.
This graph already powers backlinks, connected filters, and content discovery throughout the site.
However, there is currently no way to visually see the graph itself. A visualization would:
- Reveal the structure and interconnectedness of the content at a glance
- Enable exploration by clicking through nodes to discover related content
- Showcase the depth of the knowledge graph to visitors
- Help identify clusters, central nodes, and isolated content during authoring
The main libraries for interactive graph visualization in the browser are:
- D3.js (
d3-force): The foundational force simulation library. Maximum flexibility but low-level — requires manual rendering, zoom/pan, and React integration. Fights React's declarative model over DOM ownership. - react-force-graph: React wrapper around
force-graph(which usesd3-forceinternally). Provides 2D (Canvas) and 3D (WebGL/Three.js) variants with zoom, pan, drag, hover, and click built in. However, incompatible with React 19 due to a breaking change in ref handling. - Sigma.js (
@react-sigma/core): WebGL-based, purpose-built for graph visualization. Usesgraphologyfor the graph data model and provides React hooks for events, layout, and rendering. - Cytoscape.js (
react-cytoscapejs): Mature graph library with many layout algorithms (hierarchical, circular, force). More complex API, better suited for analytical/research use cases.
Decision
Use Sigma.js with @react-sigma/core for an interactive knowledge graph visualization.
This extracts the site's real ContentGraph data at build time, serializes it as {nodes, edges},
and renders it client-side using a deterministic, seed-based layout.
The implementation:
- Color-codes nodes by type: projects (blue), blogs (orange), roles (purple), ADRs (grey), technologies (green), tags (yellow)
- Sizes nodes by connection count: more-connected nodes appear larger
- Supports filtering: toggle node types on/off to reduce visual density
- Links to content: clicking a node opens its page and highlights connections
- Shows labels on zoom: node names appear when zoomed in, avoiding clutter at overview level
- Stable Layout: Uses a synchronous force-directed algorithm (ForceAtlas2) to calculate positions once, ensuring the graph looks identical on every load avoiding jerking on filtering/interactivity etc.
- Loads lazily: uses
next/dynamicwithssr: falsesince the WebGL API requires the browser
Demo
Loading graph...
Consequences
Pros
- React 19 compatible: Sigma.js with
@react-sigma/coreworks with React 19's ref handling. - WebGL rendering: Performant for ~150 nodes with hardware-accelerated rendering.
- graphology ecosystem: The
graphologydata model provides a clean API for graph manipulation. - SSG-compatible: Data extraction happens at build time in a server component.
- Filterable: Node type toggles let users focus on specific content types.
- Deterministic: The layout is calculated with a fixed seed, ensuring the graph always looks the same to every visitor.
Cons
- No SSR: The WebGL-based renderer requires the browser. The graph shows a loading placeholder during hydration.
- Bundle size: sigma + graphology + layout worker add client-side JavaScript.
- Fixed Layout: Users cannot drag nodes to rearrange them manually, which is a trade-off for guaranteed layout stability without "jitter".
- Information density: With all node types visible, the graph can feel busy.
- Accessibility: WebGL-based rendering is not accessible to screen readers.
- Mobile experience: Interaction is optimized for mouse/trackpad (hover for details). Touch works for pan/zoom but lacks hover states.