HAR Replay
Replay a HAR file as a mock Lightdash backend so the frontend renders with exact production data. No database, warehouse, or authentication needed.
The user must provide a path to a .har file. If not provided as $ARGUMENTS, ask for it.
Step 1: Analyze the HAR file
Run a Python script to extract key information from the HAR:
pythonimport json, sys, re from collections import Counter from urllib.parse import urlparse har_path = "$HAR_FILE_PATH" with open(har_path) as f: har = json.load(f) entries = har['log']['entries'] # Extract the page URL from the HAR pages section page_path = None pages = har['log'].get('pages', []) for p in pages: title = p.get('title', '') parsed = urlparse(title) if parsed.path and parsed.path != '/': page_path = parsed.path break # Fallback: extract from referer headers if not page_path: for e in entries: for h in e['request']['headers']: if h['name'].lower() == 'referer': parsed = urlparse(h['value']) if parsed.path and parsed.path != '/': page_path = parsed.path break if page_path: break # Find the origin (first API call) origin = None for e in entries: url = urlparse(e['request']['url']) if url.path.startswith('/api/'): origin = f"{url.scheme}://{url.netloc}" break # Filter to origin entries only api_entries = [e for e in entries if origin and origin in e['request']['url']] # Find dashboard URL if present dashboard_uuid = None for e in api_entries: path = urlparse(e['request']['url']).path m = re.search(r'/dashboards/([0-9a-f-]{36})', path) if m: dashboard_uuid = m.group(1) break # Find project UUID project_uuid = None for e in api_entries: path = urlparse(e['request']['url']).path m = re.search(r'/projects/([0-9a-f-]{36})', path) if m: project_uuid = m.group(1) break # Count request types methods = Counter() api_paths = Counter() has_base64 = False post_endpoints_needing_body_match = [] for e in api_entries: method = e['request']['method'] path = urlparse(e['request']['url']).path methods[method] += 1 normalized = re.sub(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', '{uuid}', path) api_paths[f'{method} {normalized}'] += 1 if e['response']['content'].get('encoding') == 'base64': has_base64 = True # Find POST endpoints with multiple entries (need body-based matching) for key, count in api_paths.items(): if key.startswith('POST') and count > 1: post_endpoints_needing_body_match.append((key, count)) print(f"Page path: {page_path}") print(f"Origin: {origin}") print(f"Total API entries: {len(api_entries)}") print(f"Project UUID: {project_uuid}") print(f"Dashboard UUID: {dashboard_uuid}") print(f"Has base64 content: {has_base64}") print(f"Methods: {dict(methods)}") print(f"POST endpoints needing body match: {post_endpoints_needing_body_match}") print() print("API paths:") for p, c in sorted(api_paths.items()): print(f" {p}: {c}")
Report the findings to the user:
- Page path extracted from the HAR (this is the URL the user was viewing when the HAR was captured)
- Origin domain
- Number of API entries
- Notable POST endpoints that need body-based matching (e.g.,
dashboard-chartwith multiple chart tiles)
Step 2: Write a tailored replay server
Based on the analysis, write a replay server to scripts/har-replay-server.ts. The server must handle these concerns:
Core structure
typescriptimport http from 'node:http'; import fs from 'node:fs'; import path from 'node:path';
Use only Node.js built-in modules. Run with npx tsx scripts/har-replay-server.ts <har-path>.
HAR parsing rules
- Filter by origin: Only index entries matching the detected origin domain
- Base64 decoding: Check
entry.response.content.encoding === 'base64'and decode withBuffer.from(text, 'base64').toString('utf-8') - Skip pending poll responses: For
GET /api/v2/projects/{uuid}/query/{uuid}endpoints, parse the response body and skip entries whereresults.status === 'pending'— only keep the finalreadyresponse - Normalize 304s to 200: Serve
304responses as200(they have full body in HAR) - Strip transport headers: Remove
:pseudo-headers,transfer-encoding,content-encoding,content-lengthfrom response headers
Request matching strategy
- GET requests: Match by exact
"METHOD /path?query". Fall back to path without query string. - POST
dashboard-chart: These all hit the same URL but carry differentchartUuidin the request body. Index bychartUuidfrom the HAR request body, and match incoming requests by parsing their body. - POST
dashboard-sql-chart: Same pattern but withsavedSqlUuid. If there's only one, exact path match works. - Other POST requests (e.g.,
availableFilters): Usually unique paths, exact match works. - Any POST endpoint the analysis identified as having multiple entries to the same path: Must use body-based matching on a distinguishing field.
Server behavior
- Listen on port 3001
- Log every request with method, path, and whether it matched
- Return
{"status":"error","error":{"message":"HAR replay: no matching entry"}}for 404s - On startup, print the number of indexed responses and the dashboard URL to navigate to
Step 3: Start the replay server and frontend
- Kill any existing processes on port 3001
- Start the replay server in the background:
bash
npx tsx scripts/har-replay-server.ts <har-path> - Start the Vite frontend dev server pointing at the replay server:
bash
PORT=3001 pnpm -F frontend dev - Wait for both to be ready, then verify with:
bash
curl -s http://localhost:3001/api/v1/health
Step 4: Provide the URL
Once both servers are confirmed running, tell the user the URL to open in their browser. Construct it from the page path extracted in Step 1:
http://localhost:<vite-port><page-path>
For example, if the HAR was captured on /projects/abc-123/dashboards/def-456/view, the URL would be http://localhost:3002/projects/abc-123/dashboards/def-456/view.
Note: Vite may pick a port other than 3000 since 3001 is in use. Check the Vite startup output for the actual port.
Step 5: Debug any rendering errors
If the user reports errors:
- Check the replay server logs for 404s (missing HAR entries)
- Check for response format issues (base64 encoding, unexpected MIME types)
- Fix the replay server script and restart
Notes
- The replay server is disposable — it's tailored to the specific HAR file and can be deleted after use
- HAR files may contain session cookies and sensitive data — do not commit them
- The frontend will 404 on any navigation away from the captured pages
- To re-capture: Chrome DevTools > Network tab > right-click > "Save all as HAR with content"


