This guide is the architectural and operational reference for JSON Crack — the open-source data visualizer at jsoncrack.com. It is written for engineers self-hosting the web app, developers integrating the jsoncrack-react component, contributors working in the monorepo, and architects evaluating how the system is built. End-user task documentation lives in the companion User Guide.
JSON Crack is a Turborepo + pnpm monorepo that produces four artifacts from one shared rendering core: a Next.js web application (statically exported and served by nginx), a VS Code extension, a Chrome extension, and a published React component. There is no backend service — all parsing, conversion, querying, and rendering happen client-side.
1. Executive Summary
| Attribute | Value |
|---|---|
| Project | JSON Crack — "the open-source JSON Editor" |
| License | Apache-2.0 (monorepo & web app); MIT (VS Code extension) |
| Repository | AykutSarac/jsoncrack.com |
| Architecture | Turborepo monorepo, 3 apps + 1 published package |
| Primary deliverable | apps/www — Next.js 16 app, static export, served by nginx on port 8080 |
| Rendering core | packages/jsoncrack-react — React + reaflow (ELK layout) canvas |
| State management | Zustand |
| UI toolkit | Mantine 8 + styled-components |
| Runtime requirements | Node.js ≥ 24, pnpm ≥ 10 (build-time only; output is static) |
| Backend / database | None — fully client-side; no server-side data storage |
| Analytics | Google Analytics via nextjs-google-analytics (hosted site only) |
Operational profile. Because the web app is a static export, "operating" it means serving a directory of HTML/JS/WASM assets behind any static web server. The provided Docker image bakes the build into nginxinc/nginx-unprivileged. There are no migrations, no secrets, no stateful services, and no horizontal-scaling concerns beyond standard static-asset CDN practices.
2. Architecture
2.1 Monorepo layout
jsoncrack.com/ (Turborepo root, pnpm workspaces)
├── apps/
│ ├── www/ Next.js web app → jsoncrack.com (PRIMARY deliverable)
│ ├── vscode/ VS Code extension (jsoncrack-vscode v5.1.0)
│ └── chrome-extension/ Chrome MV3 extension (v0.1.2)
├── packages/
│ └── jsoncrack-react/ Published npm package (v1.0.3) — the canvas
├── turbo.json Task graph (build/dev/lint/test)
├── pnpm-workspace.yaml Workspace globs
└── package.json Root scripts (turbo run ...)
Workspace dependencies are wired with pnpm's workspace:* protocol: all three apps depend on jsoncrack-react as a local workspace package, so the canvas is built once and consumed everywhere.
2.2 Data flow (web app)
┌─────────────────────────── apps/www (browser) ───────────────────────────┐
user input ──▶ │ TextEditor (Monaco) │
(type/paste/ │ │ onChange │
import/URL) │ ▼ │
│ useFile (Zustand) ──contentToJson()──▶ useJson (Zustand) │
│ │ (jsonAdapter: js-yaml / fast-xml-parser / json-2-csv / jsonc) │
│ ▼ debounced 400ms │
│ GraphView ──▶ <JSONCrack json={...}> (jsoncrack-react) │
│ │ parseGraph() (jsonc-parser → nodes/edges) │
│ ▼ │
│ reaflow <Canvas> + ELK layout ──▶ SVG graph │
│ ▲ │
│ useGraph (Zustand) ─┘ (viewport, direction, collapse, ref API) │
└────────────────────────────────────────────────────────────────────────────┘
Key points:
useFileowns the raw text and its format; it converts content to a normalized JSON object viajsonAdapter, then pushes a stringified, pretty-printed JSON intouseJsonon a 400 ms debounce.useJsonholds the canonical JSON string that the graph renders.GraphViewsubscribes touseJsonand renders theJSONCrackcomponent from the workspace package, wiring viewport/collapse callbacks intouseGraph.- The actual graph construction (
parseGraph) and layout (ELK viareaflow) live entirely insidejsoncrack-react.
2.3 The four products and what they share
packages/jsoncrack-react (the canvas: parser + reaflow + theme)
▲ ▲ ▲
┌─────────────────┘ │ └────────────────────┐
apps/www apps/vscode apps/chrome-extension
Next.js editor: full Webview (Vite+React) driven Content script injects a
toolset, stores, tools, by an esbuild extension host Raw|Graph toggle on JSON
conversion, export, embed via postMessage pages; mounts the canvas
Each product wraps the same <JSONCrack> component but supplies its own data source and chrome:
- www — rich editor with Monaco, Zustand stores, conversion, query tools, export, and embedding.
- vscode — a Vite-built React webview that receives the document text from the extension host over
postMessage. - chrome-extension — a content script that detects JSON pages and lazy-loads the canvas into an overlay.
3. Technology Stack & Requirements
3.1 Build/runtime requirements
| Tool | Version | Scope |
|---|---|---|
| Node.js | ≥ 24 (root engines) |
Build only |
| pnpm | ≥ 10 (pinned pnpm@10.20.0) |
Package management |
| Turbo | 2.8.x |
Task orchestration |
| VS Code | ≥ 1.100.0 | VS Code extension host |
| React (peer) | ≥ 18 | jsoncrack-react consumers |
The hosted web app is a static export — once built there is no Node runtime in production (nginx serves files).
3.2 Web app (apps/www) key dependencies
| Dependency | Role |
|---|---|
next 16 |
Framework; configured for static export (output: "export") |
react / react-dom 19 |
UI runtime |
@mantine/core, @mantine/hooks, @mantine/dropzone, @mantine/code-highlight |
UI components, hooks, file dropzone, code highlighting |
styled-components |
Theming (light/dark token objects) |
@monaco-editor/react |
The text editor pane |
jsoncrack-react (workspace) |
The graph canvas |
reaflow |
Graph rendering + ELK layout (transitive via the package) |
react-json-tree |
The Tree view |
allotment |
Resizable split panes |
zustand |
State stores |
jsonc-parser |
Tolerant JSON parsing |
js-yaml, fast-xml-parser, json-2-csv |
Format conversion |
jsonpath-plus |
JSON Path tool |
jq-web |
In-browser jq (WASM) |
json_typegen_wasm, gofmt.js |
Type generation (WASM) + Go formatting |
html-to-image |
PNG/JPEG/SVG export |
react-hot-toast |
Toasts |
nextjs-google-analytics, next-seo, next-sitemap |
Analytics, SEO, sitemap |
3.3 Extension stacks
- VS Code: Vite (webview bundle) + esbuild (extension host bundle) + React 19 + Mantine + Shiki (syntax highlight).
- Chrome: Vite library build (IIFE) emitting a single
content-script.js; React 19 +jsoncrack-react.
4. Configuration Reference
JSON Crack has very little runtime configuration — by design, since it's a static client-side app. All configuration is build-time environment variables in apps/www.
4.1 Environment variables
| Variable | Defined in | Default | Effect |
|---|---|---|---|
NEXT_PUBLIC_NODE_LIMIT |
apps/www/.env |
1200 (prod), 2000 (dev) |
Maximum renderable graph nodes; exceeding it shows the "too large" overlay. Read into SUPPORTED_LIMIT (constants/graph.ts) and passed as maxRenderableNodes. |
NEXT_PUBLIC_DISABLE_EXTERNAL_MODE |
apps/www/.env.development |
false (dev) |
When true, suppresses the ToDiagram "Upgrade to Pro" link, the editor-choice modal, and the external-mode dialog. Set this for clean self-hosting. |
NEXT_TELEMETRY_DISABLED |
apps/www/.env |
1 |
Disables Next.js build telemetry. |
ANALYZE |
build env | unset | When true, wraps the build with @next/bundle-analyzer. |
NODE_ENV |
docker-compose | production |
Standard Node environment flag. |
Important
All public env vars are inlined at build time (the
NEXT_PUBLIC_prefix). Changing the node limit requires rebuilding the static export — it cannot be changed on an already-built deployment.
Note
NEXT_PUBLIC_NODE_LIMIT(a node count) and the ~300 KB upload guard inFullscreenDropzone/useFile(a byte size) are two independent limits. Raising one does not raise the other.
4.2 next.config.js
const config = {
output: "export", // static HTML export — no Node server in prod
reactStrictMode: false,
productionBrowserSourceMaps: true,
compiler: { styledComponents: true }, // SSR-safe styled-components
turbopack: { resolveAlias: { fs: { browser: "./shims/empty.ts" } } },
webpack: (config, { isServer }) => {
config.resolve.fallback = { fs: false };
config.output.webassemblyModuleFilename = "static/wasm/[modulehash].wasm";
config.experiments = { asyncWebAssembly: true, layers: true }; // for jq-web / json_typegen
if (!isServer) config.output.environment = { ...config.output.environment, asyncFunction: true };
return config;
},
};
Notable choices: static export (output: "export") means the app ships as a folder of files; async WebAssembly is enabled so jq-web and json_typegen_wasm load; and the fs module is stubbed for the browser.
4.3 Turborepo task graph (turbo.json)
| Task | dependsOn |
Cached outputs |
|---|---|---|
build |
^build |
.next/**, dist/**, out/**, build/** |
dev |
^build |
not cached, persistent |
lint |
^build |
none |
test |
^build |
none |
analyze |
^build |
.next/**, out/**, build/** |
The ^build dependency ensures the jsoncrack-react package is built before any app that consumes it.
5. Deployment & Self-Hosting
5.1 Docker (recommended)
The Docker assets live in apps/www. The image is a multi-stage build that prunes the monorepo to just the www workspace, installs, builds the static export, and serves it from an unprivileged nginx.
# Dockerfile (apps/www) — abridged
FROM node:24-alpine AS base
RUN apk add --no-cache libc6-compat && npm install -g pnpm@10.20.0
FROM base AS pruner
COPY . .
RUN pnpm dlx turbo@2.8.20 prune www --docker # isolate the www workspace
FROM base AS installer
COPY --from=pruner /app/out/json/ .
RUN pnpm install --frozen-lockfile --ignore-scripts
COPY --from=pruner /app/out/full/ .
RUN pnpm turbo run build # produces apps/www/out (static)
FROM nginxinc/nginx-unprivileged:stable AS production
COPY --from=installer /app/apps/www/out /app
COPY apps/www/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 8080
# docker-compose.yml (apps/www)
services:
jsoncrack:
image: jsoncrack
container_name: jsoncrack
build: { context: ., dockerfile: Dockerfile }
ports: ["8888:8080"]
environment: [NODE_ENV=production]
Build and run:
cd apps/www
docker compose build
docker compose up
[+] Running 1/1
✔ Container jsoncrack Started
The app is then served at http://localhost:8888 (container port 8080).
Tip
The base image is
nginxinc/nginx-unprivileged, which runs nginx as a non-root user and listens on 8080 (not 80). Map it to whatever host port you like; the compose file uses 8888.
5.2 nginx routing
Because Next.js static export emits per-route HTML files, nginx maps the SPA routes explicitly:
server {
listen 8080;
root /app;
include /etc/nginx/mime.types;
location /editor { try_files $uri /editor.html; }
location /widget { try_files $uri /widget.html; }
location /docs { try_files $uri /docs.html; }
}
5.3 Deploy checklist
- [ ] Node ≥ 24 and pnpm ≥ 10 available on the build host (or use the Docker
node:24-alpinebase). - [ ] Set
NEXT_PUBLIC_NODE_LIMITfor your hardware before building. - [ ] Set
NEXT_PUBLIC_DISABLE_EXTERNAL_MODE=trueto remove ToDiagram promos in a self-hosted/branded deployment. - [ ] Build the static export (
pnpm build:wwwor the Dockerinstallerstage). - [ ] Serve
apps/www/outfrom any static web server (the provided nginx config handles/editor,/widget,/docs). - [ ] Front with a CDN/reverse proxy for TLS and caching of the immutable
static/assets.
5.4 Build outputs
| Command | Output |
|---|---|
pnpm build:www |
apps/www/out/ (static HTML/JS/WASM) |
pnpm build:vscode |
apps/vscode/build/ (webview + extension.js) |
pnpm build:chrome |
apps/chrome-extension/dist/ (content-script.js + manifest) |
pnpm --filter jsoncrack-react build |
packages/jsoncrack-react/dist/ (ESM + .d.ts + CSS) |
6. Web App Internals (apps/www)
6.1 Pages (routes)
| Route | File | Purpose |
|---|---|---|
/ |
pages/index.tsx |
Marketing landing page (hero, features, FAQ); fetches GitHub star count at build via getStaticProps |
/editor |
pages/editor.tsx |
The full editor (split pane, toolbar, modals) |
/widget |
pages/widget.tsx |
Embeddable graph-only view driven by postMessage and ?json= |
/docs |
pages/docs.tsx |
Embedding documentation (iframe / postMessage / React) |
/legal/privacy, /legal/terms |
pages/legal/* |
Legal pages rendered from data/*.json |
/404, /_error |
pages/404.tsx, pages/_error.tsx |
Error pages |
6.2 State stores (Zustand)
| Store | File | Responsibility |
|---|---|---|
useFile |
store/useFile.ts |
Raw text, format, validation error, JSON Schema; converts content↔JSON via jsonAdapter; session-storage persistence (<80 KB, non-iframe); URL fetch; debounced push to useJson |
useJson |
store/useJson.ts |
The canonical JSON string + loading flag that the graph consumes |
useConfig |
store/useConfig.ts |
UI prefs — darkmodeEnabled, liveTransformEnabled, gesturesEnabled, rulersEnabled; persisted to localStorage key config |
useModal |
store/useModal.ts |
Open/closed state for each named modal (Import, Download, JQ, JPath, Schema, Type, NodeModal) |
useGraph |
features/editor/views/GraphView/stores/useGraph.ts |
Viewport, layout direction, fullscreen, selected node, collapsed count; proxies imperative calls (zoomIn, centerView, collapseAll, …) to the JSONCrackRef |
6.3 Format conversion (lib/utils/jsonAdapter.ts)
A single contentToJson(value, format) / jsonToContent(json, format) pair powers all conversion. Parsers are dynamically imported so each format's library only loads when used:
| Format | Parse | Serialize |
|---|---|---|
| JSON | jsonc-parser (tolerant) |
JSON.stringify |
| YAML | js-yaml load |
js-yaml dump |
| XML | fast-xml-parser XMLParser ($ attr prefix) |
XMLBuilder |
| CSV | json-2-csv csv2json |
json2csv |
Switching the bottom-bar format selector calls useFile.setFormat, which round-trips the content through the previous format's parser and the new format's serializer.
6.4 The graph wiring (GraphView)
features/editor/views/GraphView/index.tsx renders <JSONCrack> from the package and binds:
json←useJson.jsontheme←useConfig.darkmodeEnabledlayoutDirection←useGraph.directionshowGrid←useConfig.rulersEnabledtrackpadZoom←useConfig.gesturesEnabledmaxRenderableNodes←SUPPORTED_LIMIT(or 1500 fallback)onNodeClick→ opensNodeModaland stores the selected noderenderNodeLimitExceeded→ theNotSupportedoverlay
6.5 Tools (the Tools menu)
| Tool | Implementation |
|---|---|
| JSON Query (jq) | hooks/useJsonQuery.ts → dynamic import("jq-web"), jq.promised.json() |
| JSON Path | JPathModal → jsonpath-plus JSONPath({ path, json }) |
| JSON Schema | SchemaModal → sets useFile.jsonSchema; Monaco validates against it (TextEditor registers it via jsonDefaults.setDiagnosticsOptions) |
| Generate Type | TypeModal → dynamic import("json_typegen_wasm") for TS/Kotlin/Rust/JSON-Schema; lib/utils/json2go.js + gofmt.js for Go |
6.6 Search (useFocusNode)
hooks/useFocusNode.ts runs a debounced (300 ms) DOM query for span[data-key*='<term>' i], highlights matches via CSS classes (lib/utils/search.ts), and flies the camera to the active match using the viewport's centerFitElementIntoView. It exposes next/prev/clear and match counters consumed by SearchInput.
7. jsoncrack-react — Full API Reference
The published package (packages/jsoncrack-react, v1.0.3) is the reusable canvas. Its public surface is its barrel export:
// packages/jsoncrack-react/src/index.ts
export { JSONCrack } from "./JSONCrackComponent";
export { parseGraph } from "./parser";
export type { JSONCrackProps, JSONCrackRef } from "./JSONCrackComponent";
export type { ParseGraphResult } from "./parser";
export type { CanvasThemeMode, EdgeData, GraphData, LayoutDirection, NodeData, NodeRow } from "./types";
7.1 <JSONCrack> props
| Prop | Type | Default | Description |
|---|---|---|---|
json |
string | object |
— (required) | Data to visualize. Objects are stringified (memoized per reference). |
theme |
"light" | "dark" |
"dark" |
Canvas & node color theme. |
layoutDirection |
"LEFT" | "RIGHT" | "DOWN" | "UP" |
"RIGHT" |
ELK node placement direction. |
showControls |
boolean |
true |
Render the built-in zoom/focus control overlay. |
showGrid |
boolean |
true |
Draw the background grid. |
trackpadZoom |
boolean |
false |
Treat two-finger trackpad gestures like touch (pinch-zoom). |
centerOnLayout |
boolean |
true |
Auto fit-to-center after each ELK layout pass. |
maxRenderableNodes |
number |
1500 |
Hard cap; exceeding it triggers the limit overlay. |
className |
string |
— | Extra class on the canvas wrapper. |
style |
CSSProperties |
— | Extra inline style merged onto the wrapper. |
onNodeClick |
(node: NodeData) => void |
— | Fired when a node is clicked. |
onParse |
(graph: GraphData) => void |
— | Fired with nodes/edges after each successful parse. |
onParseError |
(error: Error) => void |
— | Fired on any parse/graph-construction error. |
onViewportCreate |
(viewPort: ViewPort) => void |
— | Fired once the internal viewport is created. |
renderNodeLimitExceeded |
(count, max) => ReactNode |
— | Custom overlay when the node limit is exceeded. |
collapsedPaths |
string[] |
— | Controlled collapsed-path set (serialized keys). Requires onToggleCollapse. |
onToggleCollapse |
(path: JSONPath) => void |
— | Mutator for controlled collapse mode. |
onCollapseChange |
(paths: string[]) => void |
— | Observe collapsed-path set in uncontrolled mode. |
7.2 JSONCrackRef (imperative handle)
Obtain via a ref; methods control the viewport and collapse state:
| Method | Description |
|---|---|
zoomIn() / zoomOut() |
Step zoom while keeping center fixed |
setZoom(factor) |
Set absolute zoom at current center |
centerView() |
Fit the whole graph in the viewport |
focusFirstNode() |
Center/zoom on the root node |
toggleCollapse(path) |
Toggle one JSON path (uncontrolled mode) |
collapseAll() / expandAll() |
Collapse first-level containers / clear collapse |
getCollapsedPaths() |
Current collapsed-paths snapshot |
7.3 parseGraph(json: string): ParseGraphResult
The pure parser (also exported standalone). Uses jsonc-parser's parseTree and walks it into { nodes, edges, errors }. Arrays and objects become container nodes; primitive rows are grouped into their parent node's text rows. Node sizes are computed via calculateNodeSize. Returns collected ParseError[] rather than throwing.
7.4 Core types
type LayoutDirection = "LEFT" | "RIGHT" | "DOWN" | "UP";
type CanvasThemeMode = "light" | "dark";
interface NodeRow { key: string | null; value: string|number|null|boolean; type: Node["type"]; childrenCount?: number; to?: string[]; }
interface NodeData { id: string; text: NodeRow[]; width: number; height: number; path?: JSONPath; parentKey?: string; parentType?: string; }
interface EdgeData { id: string; from: string; to: string; text: string | null; }
interface GraphData { nodes: NodeData[]; edges: EdgeData[]; }
7.5 Theming
theme.ts exports a themes record keyed by "light"/"dark". canvasHelpers.buildCanvasStyle projects those tokens onto CSS custom properties (--node-key, --node-value, --node-integer, etc.) on the canvas wrapper, so theming is pure CSS variables — no re-render of nodes is needed to recolor.
7.6 Rendering & layout internals
- The canvas uses
reaflow's<Canvas>with a fixedlayoutOptionsELK config (NETWORK_SIMPLEXnode placement, edge-length compaction). It runs inreadonly, non-pannable/non-zoomable mode; panning/zooming is delegated toreact-zoomable-ui's<Space>. - Collapse is computed in JS: collapsed paths are serialized JSON arrays; nodes whose path is prefixed by a collapsed path are filtered out before being handed to reaflow.
- Fit-to-view (
canvasHelpers.fitGraphToViewPort) measures the painted graph via a four-step cascade (union of leaf client rects → content-group rect →getBBoxthrough CTM → ELK layout box) and centers the viewport on it. - After a collapse toggle, a
requestAnimationFrameloop re-pins the clicked button under the cursor once the layout stabilizes, avoiding a visual jump.
8. VS Code Extension Internals (apps/vscode)
The extension has two halves: an extension host (Node, bundled by esbuild → build/extension.js) and a webview (React app bundled by Vite → build/webview/).
8.1 Commands & contribution points
Declared in apps/vscode/package.json:
| Command ID | Title | Trigger |
|---|---|---|
jsoncrack-vscode.start |
Enable JSON Crack visualization | Editor title-bar icon (shown for .json / editorLangId == json) |
jsoncrack-vscode.start.selected |
Open with JSON Crack | Editor context menu, when there's a selection |
jsoncrack-vscode.start.specific |
Enable JSON Crack visualization for specific file | Programmatic (hidden from palette) |
Activation event: workspaceContains:**/*.{json}.
8.2 Host ↔ webview messaging
ext-src/extension.ts registers the commands; each creates a webview panel (ext-src/webview.ts) beside the active editor and streams the document text in:
extension host webview (App.tsx)
createWebviewPanel() ───────────────▶ render <JSONCrack json={...}>
onDidReceiveMessage("ready") ◀──────── postMessage("ready")
postMessage({ json }) ──────────────▶ setJson(...) (re-renders graph)
onDidChangeTextDocument ────────────▶ postMessage({ json }) (live updates)
createWebviewForSelectedText sends only the selected range and keeps it in sync on edits. The webview reads the editor theme from document.body[data-vscode-theme-kind] and forces the matching Mantine color scheme.
8.3 Webview security (CSP)
webview.ts builds the panel HTML with a strict Content-Security-Policy and a per-load nonce:
default-src 'self' <cspSource> blob:
connect-src <cspSource> blob:
script-src 'unsafe-eval' 'unsafe-inline' <cspSource>
style-src <cspSource> 'unsafe-inline'
worker-src <cspSource> blob: data:
enableScripts: true and retainContextWhenHidden: true are set; resources are restricted to the build/webview and assets directories. 'unsafe-eval' is required for the ELK/WASM layout path.
9. Chrome Extension Internals (apps/chrome-extension)
A Manifest v3 extension (v0.1.2) consisting of a single content script.
9.1 Manifest
{
"manifest_version": 3,
"name": "JSON Crack",
"content_scripts": [
{ "matches": ["<all_urls>"], "js": ["content-script.js"], "run_at": "document_idle" }
]
}
It declares no host_permissions beyond the content-script match and no background service worker.
9.2 Behavior
src/content-script.tsx:
- Runs only in the top frame, only when
document.contentTypeincludesjsonand the body parses as JSON. - Injects a floating Raw | Graph segmented control plus a hidden overlay container, and inlines both the package stylesheet and a local stylesheet.
- On Graph, lazily
import("jsoncrack-react")and mounts<JSONCrack>into the overlay; on Raw (or Esc) it unmounts. - Clicking a node opens a self-contained modal (content + JSON path, each with copy).
- Over-limit graphs render a "too large" upgrade card.
9.3 CSP/Worker workaround
Many JSON responses are served under a strict default-src 'none' CSP, which blocks ELK's attempt to spawn a Web Worker. The script temporarily shadows globalThis.Worker = undefined during the dynamic import and initial layout so ELK falls back to its synchronous path, then restores Worker so the host page is unaffected. The Vite build also injects a banner defining Worker = undefined and a minimal process.env.
10. Security Analysis
| Area | Assessment |
|---|---|
| Data residency | All parsing/conversion/rendering is client-side. The web app is a static export with no backend; documents are never uploaded. Matches the public privacy claims. |
| Persistence | Web app: editor content cached in sessionStorage (only <80 KB, non-iframe) and UI prefs in localStorage (config). No cookies beyond a 1-day editor_choice_shown marker. Chrome extension: no persistent storage of page JSON. |
| Network egress | Web app: outbound requests only when the user supplies a URL (?json= / Import) or on the landing page's build-time GitHub star fetch; plus Google Analytics on the hosted site. Monaco is loaded from unpkg.com CDN. Extensions: VS Code is fully offline; Chrome makes no automatic transmission of page JSON. |
dangerouslySetInnerHTML |
TreeView/TextRenderer.tsx linkifies URL-like strings by building <a> markup and injecting it via dangerouslySetInnerHTML. Inputs are user-supplied data values. This is a deliberate feature but is the single most security-sensitive code path; downstream forks should sanitize if rendering untrusted data in a shared context. |
| Webview CSP | The VS Code webview uses a strict CSP with a per-load nonce and locked-down resource roots, but allows 'unsafe-eval'/'unsafe-inline' (required for the WASM layout engine). |
| Secrets | None. There are no API keys, auth tokens, or server credentials anywhere in the deployable. |
Caution
If you fork JSON Crack to visualize untrusted, attacker-controlled data inside a privileged page, review the
TextRendererlinkify path — it interpolates string values into HTML. The upstream use case (a sandboxed editor showing the user's own data) makes this low-risk, but the calculus changes in an embedded/multi-tenant context.
11. Performance Tuning
| Lever | Where | Effect |
|---|---|---|
maxRenderableNodes / NEXT_PUBLIC_NODE_LIMIT |
prop / env | Caps graph size before the layout engine is invoked; the primary guard against jank. |
| Live Transform off | bottom bar (useConfig.liveTransformEnabled) |
Stops per-keystroke re-parse/re-layout for large documents. |
| Debounced JSON push | useFile (400 ms) |
Coalesces rapid edits before re-rendering the graph. |
| Memoized parse & style | jsoncrack-react |
toJsonText caches stringified objects (WeakMap); class/style memoized so reaflow doesn't see new objects each render. |
| Collapse before fit | JSONCrackComponent |
Hidden nodes are filtered out before reaflow/ELK runs, reducing layout cost on large graphs. |
Tip
ELK layout cost grows superlinearly with node count. For consistently large data, lower
NEXT_PUBLIC_NODE_LIMITrather than raising it — the overlay is a better UX than a multi-second freeze.
12. Operations
12.1 Common commands (from repo root)
# Web app
pnpm dev:www # next dev on :3000
pnpm build:www # static export → apps/www/out
# VS Code extension
pnpm dev:vscode # watch build
pnpm build:vscode
# Chrome extension
pnpm dev:chrome # vite build --watch → apps/chrome-extension/dist
pnpm build:chrome
# All workspaces
pnpm dev | pnpm build | pnpm lint | pnpm test
12.2 Loading the Chrome extension for testing
1. pnpm build:chrome
2. chrome://extensions → enable Developer mode
3. Load unpacked → select apps/chrome-extension/dist
12.3 Debugging the VS Code extension
- Open the monorepo root in VS Code.
- Press F5 and pick Run VSCode Extension (apps/vscode).
- In the Extension Development Host, open a
.jsonfile and run JSON Crack: Enable JSON Crack visualization. Reload the host with Ctrl/⌘+R after changes.
12.4 Tests
Only jsoncrack-react ships unit tests (Vitest): parser.test.ts, canvasHelpers.test.ts, CollapseContext.test.ts. Run with pnpm --filter jsoncrack-react test. The apps have no automated test suite — contributions are verified manually with screenshots/recordings per the contributing guide.
13. Code Patterns & Conventions
- Workspace package as shared core. The graph lives in one published package consumed by all apps via
workspace:*; never duplicate rendering logic into an app. - Zustand slices by concern. Separate stores for file, json, config, modal, and graph rather than one global store;
useConfigis the only persisted one. - Dynamic imports for heavy/optional code.
jq-web,json_typegen_wasm,js-yaml,fast-xml-parser,json-2-csv, and the editor/modals are allimport()-ed on demand to keep the initial bundle small. - Controlled vs. uncontrolled collapse.
jsoncrack-reactsupports both; providecollapsedPaths+onToggleCollapseto control it, or omit them to let the component manage state internally. - Theme via CSS custom properties. Recoloring is a style change, not a React re-render.
- Code style: Google TypeScript Style Guide; ESLint + Prettier with
@trivago/prettier-plugin-sort-imports. Lint runstscfirst.
14. Known Issues & Potential Improvements
Drawn from the community issue tracker and code observations:
| Theme | Detail | Source |
|---|---|---|
| Large-file handling | Big JSON (e.g. >2,500 lines) hits the node limit; requests for partial/streamed rendering recur. | #513, #234, #337 |
| Numeric precision | int64+ values lose precision through JS parsing. | #462 |
| Paste truncation | Pasting can truncate JSON at certain positions. | #463 |
| Blank-line handling | Confusion over whitespace in otherwise-valid JSON. | #509 |
| SVG→PDF export | SVG export can require external resources, breaking PDF conversion; PNG is the reliable path. | #421 |
| Offline editor | Requests for a fully offline/CSP-friendly self-hosted editor and a published Docker image. | #368, #226, #459, #434 |
| Format support | Requests for JSON5, .xlsx import, encoding selection. |
#331, #414, #464 |
| Feature requests | Copy-JSON in tools menu, jump-to-code from diagram, table view of nested JSON, per-node colors. | #576, #570, #343, #417 |
| Monaco from CDN | TextEditor loads Monaco from unpkg.com — a hard external dependency that breaks in air-gapped/offline deployments. |
code |
Note
Several "offline" requests are partially addressed by the documented Docker self-host path, but the hosted editor's Monaco CDN dependency (
unpkg.com) and Google Analytics remain external touchpoints to remove for a truly air-gapped build.
15. Contributing
The project uses an issue-first workflow: open or find an issue before coding, get it approved, then submit a PR linked with Closes #123. PRs must include a clear description and visual evidence (screenshot or recording), pass local pnpm dev verification, and follow the Google TypeScript Style Guide. The performance guideline is explicit: avoid unnecessary re-renders. See CONTRIBUTING.md and CODE_OF_CONDUCT.md in the repo.
16. Quick Reference
Ports & paths
| Item | Value |
|---|---|
| nginx listen port (container) | 8080 |
| docker-compose host port | 8888 |
| Web app static output | apps/www/out |
| Dev server | http://localhost:3000 |
Environment variables
| Var | Default |
|---|---|
NEXT_PUBLIC_NODE_LIMIT |
1200 (prod) / 2000 (dev) |
NEXT_PUBLIC_DISABLE_EXTERNAL_MODE |
false |
NEXT_TELEMETRY_DISABLED |
1 |
ANALYZE |
unset |
Package versions
| Package | Version |
|---|---|
jsoncrack-vscode |
5.1.0 |
jsoncrack-react |
1.0.3 |
| chrome-extension | 0.1.2 |
| next | 16 |
| react | 19 |
Workspace map
| Path | Artifact |
|---|---|
apps/www |
Next.js static site |
apps/vscode |
VS Code extension |
apps/chrome-extension |
Chrome MV3 extension |
packages/jsoncrack-react |
npm package (the canvas) |