高度なフロントエンドアーキテクチャ & DX: より高速な開発ループのためのLocalhost Tunnelsの活用
localhostトンネルを使った開発者体験の最適化方法を紹介します。ビジュアルリグレッションパイプラインの構築、フラグの同期、マイクロフロントエンドのローカルテストを学びましょう。

Quick answer
高度なフロントエンド用ローカルホストトンネルパイプラインの構築: localhost tunnel answer
A localhost tunnel gives your local app a public HTTPS URL without opening router ports, which is useful for demos, QA, mobile testing, and provider callbacks.
How do I expose localhost without opening ports?
Use a reverse HTTPS tunnel. Your machine connects outbound to the tunnel service, and the public URL forwards requests back to your local app.
When should I use a localhost tunnel?
Use one for webhook testing, OAuth callbacks, client demos, QA previews, mobile device checks, and short-lived development reviews.
フロントエンドのフィードバックループは、各チェックにデプロイが必要なため遅くなります。プレビュー環境やCIパイプラインを待つことなく、電話でのビジュアル変更やリアルシェルに対するマイクロフロントエンドのテストを行うには、摩擦とコストが伴います。
ローカルホストトンネルは、そのループを短縮する一つの方法です。マシン上の小さなエージェントがリレーにアウトバウンド接続を行い、リレーはあなたのローカルサーバーに公開HTTPS URLを提供します。一般的な選択肢は ngrok、Cloudflare Tunnel (cloudflared)、localtunnel です。それぞれのトレードオフは異なり、以下のパターンの適用範囲を決定します。
この記事では、3つのパターンと、それぞれにおいてトンネルが役立つ場面とそうでない場面を解説します:
- Playwrightによるビジュアルリグレッションテストをトンネル経由の開発サーバーに対して実行
- 実機デバイス上でのフラグのテスト
- デプロイ済みシェル内のマイクロフロントエンドリモートの上書き(Shadow DOMによる隔離)
トンネルの選択: ドキュメントの見解(2026年10月)
トンネルを使ったワークフローを設計する前に、これらの制限を確認してください。
| ツール | 安定URL | 重要な制限 | 最適な用途 |
|---|---|---|---|
| ngrok (無料) | 割り当てられた開発用ドメイン(例: your-name.ngrok-free.app); 名前は選べません |
HTMLブラウザトラフィックにインタースティシャルページ表示; カスタムまたは予約ドメインは有料プラン必要 | デバイステスト、Webhook、短いデモ |
| Cloudflare Quick Tunnel | いいえ: 毎回新しい trycloudflare.com ホスト名 |
SLAなし、トンネルあたり200リクエスト、Server-Sent Events非対応 | 一時的なHTTPアプリの共有 |
| Cloudflare Named Tunnel | はい、自ドメイン上 | Cloudflareアカウントとドメイン設定必要 | 長期安定エンドポイント |
| localtunnel | ランダムサブドメイン | パッケージ非アクティブ(最新npmリリースは約3年前); パブリックインスタンスは最善努力 | 迅速な実験や低リスクテスト |
CloudflareはQuick Tunnelsをテスト専用とし、同時リクエストの上限とSSE非サポートを明記しています。あなたのアプリがSSEをストリーミングする場合(開発ダッシュボードやAIチャットUIなど)、Quick Tunnelはそれを破壊します。名前付きトンネルや他のプロバイダーを使用してください。
ngrokの無料プランでは、HTMLブラウザトラフィックの前にインタースティシャルページが挿入され、フィッシング防止策として機能します。受け入れると7日間持続するクッキーが設定されます。ngrok-skip-browser-warningヘッダー(任意の値)を含むリクエストや有料プランではこれをバイパスします。後で重要になる点として、ページが読み込むスクリプトやアセットは、期待したファイルの代わりにインタースティシャルHTMLを受け取る可能性があります。
1. トンネル経由の開発サーバーに対するビジュアルリグレッションテスト
Playwrightはexpect(page).toHaveScreenshot()によるビジュアル比較機能を標準搭載しています。古いtoMatchSnapshot()も動作しますが、PlaywrightのドキュメントはスクリーンショットにはtoHaveScreenshot()を推奨しています。
トンネルが必要な場合と不要な場合
Playwrightがあなたの開発サーバーと同じマシン(またはCIランナー)上で動作している場合、localhostに直接アクセス可能です。PlaywrightのwebServer設定はサーバーの起動も可能です。ここにトンネルは不要です。
PercyやChromaticなどのクラウドビジュアルサービスも、あなたのマシンにアクセスする必要はありません。Playwrightとの連携(@percy/playwrightとpercy exec、@chromatic-com/playwright)は、ローカルテスト中のUIのスナップショットやアーカイブをキャプチャし、比較・レビューのためにアップロードします。Percyはこれらのスナップショットを自社クラウドにレンダリングします。
リモートのブラウザグリッドや実機クラウド、作業中のブランチのレビューなど、リモート側に何かをロードさせる必要がある場合にトンネルは役立ちます。以下の例はその仕組みを示しています。同じパターンは、リモートブラウザがローカルではなくリモートの場合にも適用可能です。
実装例: Playwright + 公式ngrok SDK
公式のNode SDKは@ngrok/ngrokです。別のバイナリは不要で、ngrok.forward()を使ってリスナーを起動します。
npm install --save-dev @playwright/test @ngrok/ngrok
npx playwright install
playwright.config.ts
import { defineConfig } from '@playwright/test';
const { PLAYWRIGHT_BASE_URL, TUNNEL_USER, TUNNEL_PASS } = process.env;
export default defineConfig({
testDir: './tests/visual',
use: {
baseURL: PLAYWRIGHT_BASE_URL ?? 'http://localhost:3000',
// テストブラウザからngrokの無料プランのインタースティシャルをスキップ
extraHTTPHeaders: { 'ngrok-skip-browser-warning': '1' },
// トンネル設定のBasic認証に応答
...(TUNNEL_USER && TUNNEL_PASS
? { httpCredentials: { username: TUNNEL_USER, password: TUNNEL_PASS } }
: {}),
},
expect: {
toHaveScreenshot: { maxDiffPixelRatio: 0.02 }, // 差異ピクセル比率最大2%
},
});
run-visual-tests.mjs
import ngrok from '@ngrok/ngrok';
import { spawnSync } from 'node:child_process';
const port = Number(process.env.PORT ?? 3000);
const { TUNNEL_USER, TUNNEL_PASS } = process.env;
const listener = await ngrok.forward({
addr: port,
authtoken_from_env: true, // NGROK_AUTHTOKENを読む
basic_auth: `${TUNNEL_USER}:${TUNNEL_PASS}`, // パブリックな開発サーバーを公開しないため
});
try {
console.log(`トンネル稼働中: ${listener.url()}`);
const result = spawnSync('npx', ['playwright', 'test'], {
stdio: 'inherit',
env: { ...process.env, PLAYWRIGHT_BASE_URL: listener.url() },
});
process.exitCode = result.status ?? 1;
} finally {
await listener.close(); // 常にトンネルを閉じる
}
tests/visual/dashboard.spec.ts
import { test, expect } from '@playwright/test';
test('ダッシュボードはベースラインと一致', async ({ page }) => {
await page.goto('/dashboard');
// ネットワークアイドルではなく、見える要素を待つ
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page).toHaveScreenshot('dashboard.png', {
fullPage: true,
mask: [page.locator('.timestamp-badge')], // 変動する領域をマスク
});
});
一般的な例からの3つの変更点:
waitForLoadState('networkidle')に頼らない。 PlaywrightのAPIドキュメントでは、その状態はテストには推奨されず、toBeVisible()などのWebアサーションを推奨しています。- 変動するコンテンツは非表示にせずマスク。
toHaveScreenshot()のmaskオプションは、タイムスタンプやアバターなどの変動領域に対応します。スクリーンショットアサーションはアニメーションをデフォルトで無効化し、2回連続のキャプチャが一致するまで待ちます。 - ベースラインの環境を一定に保つ。 OS間でフォントやアンチエイリアシングが異なるため、同じ環境(コンテナイメージの固定など)で生成・比較します。意図的に
npx playwright test --update-snapshotsで更新してください。
トンネルができることとできないこと
- メリット: 公開HTTPS URLを通じて、ツールがあなたのマシンを見られない場合でもアクセス可能にします。
- デメリット: これだけではテストのフレークを決定論的にしません。アニメーション、フォント、ライブデータのフレークは、マスクと安定したフィクスチャで解決します。
- コスト: 無料プランのインタースティシャル、リクエスト制限、URLを知っている誰でもアクセス可能な点です。
2. 実機デバイス上でのフラグのテスト
実機のiPhoneやAndroid端末でのフラグ挙動は、タッチ操作、ビューポートのクイーク、WebViewの挙動、ネットワーク条件など、デスクトップエミュレーションと異なります。トンネルを使えば、何もデプロイせずにローカルビルドを端末からアクセス可能にします。
トンネルが運ぶものと運ばないもの
これは誤解しやすいポイントです。トンネルはあなたのアプリのHTMLとJavaScriptを端末に提供します。フラグの値は別の経路を通ります:フラグSDKは端末からベンダーのクラウドに直接通信し、トンネルは関係ありません。コンソールでフラグを変更しても、トンネルを通す必要はありません。トンネルは、あなたのサーバーを経由してフラグトラフィックをプロキシする場合にのみ重要です。
ページの反応速度はSDKに依存します:
- LaunchDarklyのJavaScriptクライアントSDKは、ポーリングエンドポイントを通じて最初の接続を行い、一部(React Web SDKなど)はデフォルトでストリーミング接続を開きます。
client.on('change:flag-key', …)で変更を購読します。 - PostHogのJSライブラリは、ページごとにフラグを背景で取得し、値をキャッシュします。更新は
posthog.reloadFeatureFlags()で行います。onFeatureFlagsを使って最初のフェッチ完了を待ちます。
例:LaunchDarklyのフラグ変更に反応
// useFlag.ts
import { useEffect, useState } from 'react';
import { initialize } from 'launchdarkly-js-client-sdk';
const client = initialize(import.meta.env.VITE_LD_CLIENT_ID, {
kind: 'user',
key: 'dev-device-01',
});
export function useFlag(key: string, fallback = false) {
const [value, setValue] = useState(fallback);
useEffect(() => {
let active = true;
const refresh = () => active && setValue(client.variation(key, fallback));
client.waitForInitialization(5).then(refresh).catch(() => {});
client.on(`change:${key}`, refresh);
return () => {
active = false;
client.off(`change:${key}`, refresh);
};
}, [key, fallback]);
return value;
}
export function Checkout() {
const newCheckout = useFlag('new-checkout-v2');
return newCheckout ? <ExpressCheckout /> : <LegacyCheckout />;
}
開発またはテスト環境のクライアントIDを使い、フラグの切り替えが本番ユーザーに影響しないようにします。
例:PostHogの明示的リフレッシュ
import posthog from 'posthog-js';
posthog.init(import.meta.env.VITE_POSTHOG_KEY, {
api_host: 'https://us.i.posthog.com', // EUの場合は https://eu.i.posthog.com
});
posthog.onFeatureFlags(() => {
console.log('new-checkout-v2:', posthog.getFeatureFlag('new-checkout-v2'));
});
// フラグ変更後に最新値を取得(ボタンやinterval、visibilitychangeから呼び出し)
posthog.reloadFeatureFlags();
デバイスにローカルサーバーを公開する
ngrok http 3000
無料プランではngrokが自動的にドメインを割り当てます(名前は選べません)。端末でhttps://…ngrok-free.appを開きます。最初のアクセス時にインタースティシャルが表示され、承認すると7日間有効なクッキーが設定されます。
一部の開発サーバーは未認識のHostヘッダーを拒否します。Viteはserver.allowedHosts、webpack-dev-serverはallowedHostsを持ちます。localhostからのページはトンネル経由で読み込めない場合、まずこれを確認してください。
実用的な制限
- CloudflareのQuickトンネルは、SSEを使うアプリには適しません。
- ホットモジュールリロードはWebSocketを使います。ページが読み込めてもライブアップデートしない場合、トンネルや開発サーバーのホスト設定を確認してください。
- 実ユーザーデータや本番資格情報を公開トンネルに載せないでください。
3. マイクロフロントエンド: デプロイ済みシェル内のリモート上書き
Module Federationは、独立して構築されたアプリケーション間でコードをランタイム共有する仕組みです。Webpack 5には元のプラグインが含まれ、Module Federation 2.0はバンドラー非依存のランタイムやリモートの型ヒント、@module-federation/enhancedによるランタイムプラグインを追加します。これによりregisterRemotesやloadRemoteが動的にリモートを追加可能です。
一般的な開発の課題は、リモートは単体では動作しますが、スタイルの衝突や依存関係の不一致、認証に依存した動作など、シェル内に組み込むと問題が顕在化することです。解決策の一つは、シェルをデプロイしたままにしておき、リモートだけをローカルでトンネル経由で動かし、シェルのリクエストをリモートのエントリーファイルにリダイレクトすることです。
なぜlocalhostではなくトンネルなのか
- Chromeは、パブリックサイトからローカルネットワークやループバックアドレスへのリクエストを許可のプロンプト(Local Network Access)で制限し始め、Chrome 142以降はWebSocketも対象に拡大しました。トンネルはリモートに公開HTTPSオリジンを提供し、HTTPSシェルがパブリックなループバックリクエストを行わないようにします。
- HTTPSシェルがHTTPコンテンツを読み込むと混在コンテンツの問題が発生します。HTTPSトンネルURLを使えばこれを回避できます。
- チームメンバーも同じオーバーライドをネットワークにいなくてもロード可能です。
ステップ1: リモートの設定
// webpack.config.js (リモート)
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
module.exports = {
mode: 'development',
output: { publicPath: 'auto' }, // chunksはremoteEntry.jsの読み込み元に相対
devServer: {
port: 8081,
allowedHosts: ['.ngrok-free.app'], // トンネルのHostヘッダーを許可
headers: {
// テスト対象のシェルに限定
'Access-Control-Allow-Origin': 'https://app.example.com',
},
},
module: {
rules: [
// スコープ付きCSSを文字列としてインポートし、シャドウルートに注入
{ test: /\.scoped\.css$/, type: 'asset/source' },
],
},
plugins: [
new ModuleFederationPlugin({
name: 'checkoutMFE',
filename: 'remoteEntry.js',
exposes: { './CheckoutWidget': './src/CheckoutWidget.tsx' },
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
ステップ2: Shadow DOMによるスタイルの隔離とその制約
Shadow DOMはホストページのセレクタがコンポーネントに届かないようにし、逆も同様です。ただし、完全な隔離ではありません:
- 継承されるプロパティは境界を越える。
colorやfont-family、line-heightなどは親要素から継承され、Shadow DOM内にも適用されます。 - CSSカスタムプロパティも越境します。 テーマ設定に便利ですが、ホストの
--primaryがコンポーネントに影響します。 all: initialを:hostに設定すると継承とカスタムプロパティがリセットされます。必要に応じて明示的にリセットしてください。
// src/CheckoutWidget.scoped.css
:host {
all: initial; /* ホストページからの継承を停止 */
display: block;
}
.checkout-container { font-family: system-ui, sans-serif; }
Shadow DOMによりCSSが封じ込められるため、ホスト側のスタイル衝突は見えなくなります。ただし、継承やカスタムプロパティの前提は実際のシェルで検証してください。
ステップ3: シェルのリクエストをトンネルにリダイレクト
リモートとトンネルを起動します:
npm start # リモートは:8081
ngrok http 8081 # https://your-name.ngrok-free.app URLを確認
次に、declarativeNetRequestを使ったChrome拡張を作成し、リクエストをブロック・変更します。これにはdeclarativeNetRequestの権限と、関係するオリジンのhost_permissionsが必要です。
manifest.json
{
"manifest_version": 3,
"name": "MFE Local Override",
"version": "0.1.0",
"permissions": ["declarativeNetRequest"],
"host_permissions": ["https://app.example.com/*", "https://*.ngrok-free.app/*"],
"declarative_net_request": {
"rule_resources": [
{ "id": "mfe_override", "enabled": true, "path": "rules.json" }
]
}
}
rules.json
[
{
"id": 1,
"priority": 1,
"action": {
"type": "redirect",
"redirect": { "url": "https://your-name.ngrok-free.app/remoteEntry.js" }
},
"condition": {
"urlFilter": "||app.example.com/assets/mfe/checkout/remoteEntry.js",
"resourceTypes": ["script"]
}
},
{
"id": 2,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{ "header": "ngrok-skip-browser-warning", "operation": "set", "value": "1" }
]
},
"condition": {
"requestDomains": ["your-name.ngrok-free.app"],
"resourceTypes": ["script", "xmlhttprequest"]
}
}
]
リクエストヘッダーの設定により、インタースティシャルページの警告を回避します。fetchを使った他のファイルも対象にしたい場合はresourceTypesにxmlhttprequestを追加します。
これらの静的ルールはインストール時に固定されます。トンネルのホスト名が変わる場合は動的ルールを使います。これによりブラウザのセッションや拡張機能のアップグレード後も持続します。
このパターンの安全性について
- このオーバーライドは拡張機能をインストールしたブラウザだけに作用します。共有や本番環境に向けて設定しないでください。
- リモートはURLを知っている誰でもアクセス可能です。スクリプトのサブリソースは認証ページがなく、IP制限(ngrokの
restrict-ips)の方が適しています。 - シェルとリモートが
singleton依存関係を共有している場合、バージョン不一致(例:Reactのメジャーバージョン違い)はランタイムエラーとして現れます。共有設定を修正してください。
まとめ
| 目的 | トンネルが役立つ場面 | 役立たない場面 | 主要ツール |
|---|---|---|---|
| ビジュアルリグレッション | リモートブラウザ、デバイスクラウド、レビュアーがローカルビルドを読み込む必要がある | localhostに対して既にPlaywrightやPercy、Chromaticが動作 |
Playwright toHaveScreenshot(), @ngrok/ngrok, Percy, Chromatic |
| デバイス上のフラグテスト | 実機がローカルアプリを読み込む必要がある | フラグ値はデバイスとベンダークラウド間を流れる | LaunchDarkly, PostHog, ngrok |
| マイクロフロントエンドの上書き | HTTPSデプロイ済みシェルが公開HTTPSリモートを必要とする | Shadow DOMは継承やカスタムプロパティの越境を防止しない | Module Federation 2.0, declarativeNetRequest, Shadow DOM |
トンネルワークフローのセキュリティチェックリスト
- すべてのトンネルに認証または制限を設定。ngrokはBasic AuthやOAuth、IP制限をサポートし、
@ngrok/ngrokはbasic_authやtraffic_policyを受け入れます。Cloudflare Quick Tunnelsはデフォルトでは認証なしですが、cloudflaredの--allowed-mailオプションでメール認証を設定可能です。 - トークンはソース管理に含めない。
NGROK_AUTHTOKENやCloudflareの資格情報は環境変数や秘密管理ツールから読み込み、定期的にローテーションしてください。 - 短命のトンネルを使用。
finallyブロックやシグナルハンドラでリスナーを閉じ、クラッシュしたスクリプトが開発サーバーを露出し続けないようにします。 - 本番データや資格情報を公開トンネルに載せない。フィクスチャや開発環境を利用してください。
- 無料プランの制限を事前に確認。インタースティシャル、ドメイン制限、リクエスト制限、SSE非サポートなどの制約があります。
結論
リモート側があなたのローカルビルドにアクセスする必要がある場合にトンネルは有効です。実機、ブラウザホスティング、デプロイ済みシェル、レビュアー向けです。すべてが一台のマシン上で動作している場合や、フラグSDKやスクリーンショットサービスの通信には不要です。問題に合わせてパターンを選び、トンネルの制限を確認し、エンドポイントを適切に保護してください。
参考資料
- ngrok JavaScript SDK (
@ngrok/ngrok) - ngrok無料プランの制限とインタースティシャルページ
- ngrokネットワークセキュリティとIP制限
- Cloudflare Quick Tunnels
- Playwright
SnapshotAssertions(toHaveScreenshot()の利用) - Chromatic for Playwright
- PercyとPlaywrightの公式連携ガイド
- PostHogのWebフラグ
- LaunchDarklyのクライアントサイドとサーバーサイドSDKの挙動
- LaunchDarklyのフラグ変更購読
- Module FederationのランタイムAPI
- Chromeの
declarativeNetRequestAPI - Chromeのローカルネットワークアクセス
- Shadow DOMと継承スタイル(Open Web Components)
- localtunnelのメンテナンス状況(Snyk)
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.