har-replay について
Lightdashのフロントエンド開発者向けのHAR再生スキルです。HARファイルを一時的なモックバックエンドに変換し、データベース、データウェアハウス、認証なしで記録済みデータを使ってページを表示します。
主な機能
- 01HARのリクエストとページ情報を分析
- 02パスとリクエストボディで照合
- 03レスポンスをデコードしステータスを正規化
- 04一時的なNode.js再生サーバーを作成
- 05フロントエンドを起動してページを確認
活用シーン
- 01記録済みデータでフロントエンドを再現
- 02データベースなしでダッシュボードを確認
- 03HARの不足エントリーや応答形式を調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ lightdash/ lightdash --skill har-replayインストール後、Claude Code で/har-replayを入力して読み込まれたか確認します。
詳細
HAR リプレイ
HAR ファイルをモックの Lightdash バックエンドとして再生し、本番環境と完全に同じデータでフロントエンドを表示します。データベース、ウェアハウス、認証は不要です。
ユーザーから .har ファイルのパスを指定してもらう必要があります。$ARGUMENTS として渡されていない場合は、パスを尋ねてください。
Step 1: HAR ファイルを分析する
Python スクリプトを実行し、HAR から重要な情報を抽出します。
import 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}")
分析結果をユーザーに報告します。
- HAR から抽出したページパス(HAR を記録したときにユーザーが表示していた URL です)
- オリジンドメイン
- API エントリー数
- 本文に基づく照合が必要な、注目すべき POST エンドポイント(例:複数のチャートタイルがある
dashboard-chart)
Step 2: 要件に合わせたリプレイサーバーを作成する
分析結果に基づき、scripts/har-replay-server.ts にリプレイサーバーを作成します。サーバーでは、次の点に対応する必要があります。
基本構成
import http from 'node:http';
import fs from 'node:fs';
import path from 'node:path';
Node.js の組み込みモジュールのみを使用します。npx tsx scripts/har-replay-server.ts <har-path> で実行します。
HAR の解析ルール
- オリジンでフィルタリング:検出したオリジンドメインに一致するエントリーのみをインデックス化します
- Base64 のデコード:
entry.response.content.encoding === 'base64'を確認し、Buffer.from(text, 'base64').toString('utf-8')でデコードします - 保留中のポーリング応答をスキップ:
GET /api/v2/projects/{uuid}/query/{uuid}エンドポイントでは、応答本文を解析し、results.status === 'pending'のエントリーをスキップします。最終的なready応答のみを保持します - 304 を 200 に正規化:HAR には本文全体が含まれているため、
304応答は200として返します - 転送用ヘッダーを削除:応答ヘッダーから
:pseudo-headers、transfer-encoding、content-encoding、content-lengthを削除します
リクエストの照合方法
- GET リクエスト:「
METHOD /path?query」の完全一致で照合します。一致しない場合は、クエリ文字列なしのパスで照合します。 - POST
dashboard-chart:すべて同じ URL に送信されますが、リクエスト本文のchartUuidは異なります。HAR のリクエスト本文からchartUuidを取得してインデックス化し、受信リクエストの本文を解析して照合します。 - POST
dashboard-sql-chart:同じ方法でsavedSqlUuidを使用します。該当するエントリーが 1 つだけなら、パスの完全一致で照合できます。 - その他の POST リクエスト(例:
availableFilters):通常はパスが固有のため、完全一致で照合できます。 - 分析で同じパスへの複数エントリーが確認された POST エンドポイント:識別用フィールドを使った本文ベースの照合が必要です。
サーバーの動作
- ポート 3001 で待ち受けます
- すべてのリクエストについて、メソッド、パス、照合結果をログに記録します
- 404 の場合は
{"status":"error","error":{"message":"HAR replay: no matching entry"}}を返します - 起動時に、インデックス化した応答数と、移動先となるダッシュボード URL を表示します
Step 3: リプレイサーバーとフロントエンドを起動する
- ポート 3001 を使用している既存のプロセスを終了します
- リプレイサーバーをバックグラウンドで起動します。
npx tsx scripts/har-replay-server.ts <har-path> - リプレイサーバーを参照するように Vite フロントエンド開発サーバーを起動します。
PORT=3001 pnpm -F frontend dev - 両方の起動を待ってから、次のコマンドで確認します。
curl -s http://localhost:3001/api/v1/health
Step 4: URL を提示する
両方のサーバーが稼働していることを確認したら、ブラウザーで開く URL をユーザーに伝えます。Step 1 で抽出したページパスを使って URL を作成します。
http://localhost:<vite-port><page-path>
たとえば、HAR が /projects/abc-123/dashboards/def-456/view で記録された場合、URL は http://localhost:3002/projects/abc-123/dashboards/def-456/view です。
注:3001 が使用中のため、Vite は 3000 以外のポートを使用することがあります。Vite の起動出力で実際のポートを確認してください。
Step 5: レンダリングエラーをデバッグする
ユーザーからエラーが報告された場合:
- リプレイサーバーのログで 404(HAR エントリーの不足)がないか確認します
- 応答形式に問題がないか確認します(Base64 エンコーディング、予期しない MIME タイプなど)
- リプレイサーバーのスクリプトを修正して再起動します
注記
- リプレイサーバーは使い捨てです。特定の HAR ファイルに合わせて作成されるため、使用後に削除できます
- HAR ファイルにはセッション Cookie や機密データが含まれる場合があります。コミットしないでください
- キャプチャしたページから別のページへ移動すると、フロントエンドは 404 を返します
- 再キャプチャするには、Chrome DevTools の Network タブで右クリックし、「Save all as HAR with content」を選択します