mcpField
M
metabase-data-app-routing

metabase-data-app-routing

github.com/metabase開発49.9k800 回インストール今日更新
リポジトリ

metabase-data-app-routing について

既存の Metabase Data App にルーティングを追加するためのガイドです。SDK のルーティング API を使い、ルーターライブラリに依存せず、デフォルトページやナビゲーション、パス判定を設定します。

主な機能

  1. 01SDK のルーティング API を使用
  2. 02パスの取得とナビゲーション
  3. 03中クリックや新規タブに対応するリンク
  4. 04iframe の URL プレフィックスを自動検出
  5. 05ベースパスのデフォルトページ設定

活用シーン

  1. 01URL パスに応じてページを表示
  2. 02Data App 内にナビゲーションを追加
  3. 03ボタンから指定パスへ移動

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/metabase/metabase --skill metabase-data-app-routing

インストール後、Claude Code で/metabase-data-app-routingを入力して読み込まれたか確認します。

詳細

データアプリにルーティングを追加する

Metabase のデータアプリバンドルにはルーターライブラリは同梱されていません。代わりに @metabase/embedding-sdk-react/data-app から 3 つの小さなプリミティブをインポートします。

API目的
<DataAppRouter>アプリを一度ラップします。現在のサブパスを追跡します。iframe の URL プレフィックスを自動検出するため、バンドルの作成者が basename を書く必要はありません。
<DataAppLink to="/customers/42">内部ナビゲーションリンクです。実際の <a href> をレンダリングするため、ミドルクリック / cmd クリックで新しいタブで開けます。
useDataAppLocation(){ pathname, navigate } を返します。等値マッチ / startsWith によるレンダリングには pathname を使用し、プログラムによるナビゲーションには navigate(to) を使用します。

これがサーフェスのすべてです。どのバージョンの react-router も、<BrowserRouter> も、<HashRouter> も使いません。 この API はあえてどのルーターライブラリからも分離されているため、将来の Metabase バージョンがバンドルコードに触れることなく基盤の実装を差し替えられます。

このスキルを使う場面

  • ユーザーが動作するデータアプリプロジェクトを持っている場合 — データアプリテンプレートからスキャフォールドされているため、ワンライナーの vite.config.ts(dataAppConfig())と、ファクトリをエクスポートする src/index.tsx がすでに存在します。開発プレビューにはプロジェクト内のエントリがありません。SDK の開発プリセットがそれを配信します。
  • ユーザーがバンドルで異なる URL(/overview、/customers/:id)に異なるコンテンツをレンダリングしたい場合。
  • このスキルを、プロジェクトをゼロからスキャフォールドするために使わないでください — これは既存のデータアプリプロジェクトにのみパッチを当てます。まだプロジェクトがない場合は、中断して、ルーティングを追加する前に新しいデータアプリのスキャフォールドから始めるようユーザーに伝えてください。

dataAppConfig() はすでに @metabase/embedding-sdk-react/data-app を外部化しています。ルーティングを追加するために vite.config.ts を編集する必要はありません — 以下の手順に従って src/App.tsx(および必要に応じて追加のコンポーネントファイル)を編集するだけです。

ステップ 1 — <DataAppRouter> で App.tsx をラップする

ルーティングプリミティブを通常どおりインポートします。<DataAppRouter> は basename プロパティを取りません — 本番環境では iframe の URL プレフィックス(/embed/apps/<name>)を自動検出し、Vite 開発プレビューではプレフィックスなしに解決します。

App.tsx は純粋なコンテンツです — ここに <MetabaseProvider> はありません。SDK の開発プレビューエントリ(DataAppDevProvider)と本番ホスト(DataAppProvider)が、それぞれ独自のレルムでツリーをラップします。

import { StaticQuestion } from "@metabase/embedding-sdk-react";
import {
  DataAppRouter,
  DataAppLink,
  useDataAppLocation,
} from "@metabase/embedding-sdk-react/data-app";

function Nav() {
  return (
    <nav style={{ padding: 16, borderBottom: "1px solid #e5e7eb" }}>
      <DataAppLink to="/" style={{ marginRight: 16 }}>Overview</DataAppLink>
      <DataAppLink to="/customers/42">Customer 42</DataAppLink>
    </nav>
  );
}

function Page() {
  const { pathname } = useDataAppLocation();

  if (pathname === "/") {
    return (
      <div style={{ padding: 24 }}>
        <h1>Overview</h1>
        <StaticQuestion questionId={1} height={360} />
      </div>
    );
  }

  const customerMatch = pathname.match(/^\/customers\/(\d+)$/);
  if (customerMatch) {
    const id = Number(customerMatch[1]);
    return (
      <div style={{ padding: 24 }}>
        <h1>Customer #{id}</h1>
        <StaticQuestion questionId={id} height={360} />
      </div>
    );
  }

  return <div style={{ padding: 24 }}>Not found: {pathname}</div>;
}

export default function App() {
  return (
    <DataAppRouter>
      <Nav />
      <Page />
    </DataAppRouter>
  );
}

yarn dev を実行し、リンクをクリックして、URL バーが変わるのを確認します。http://localhost:5174/customers/42 でリロードすると、開発プレビューは直接 customer ルートに着地します。

読み込み時に常にデフォルト(左端)のタブを事前選択する

アプリが複数のタブ(または任意のトップレベルページスイッチャー)を提示する場合、ベースパス / は必ずデフォルト — 左端 / 最初 — のタブのコンテンツをレンダリングしなければならず、空白ページ、「Not found」、空のシェルを決して表示してはいけません。 これは最もよくある間違いです。アプリは / で起動し、どのブランチもマッチせず、ユーザーがタブをクリックするまで何も表示されません。最初のタブを選択することをユーザー(または後続のナビゲーション)に頼らないでください — デフォルトのタブが初期状態です。

タブがルートに基づくかどうかに応じて、それを保証する 2 つの同等の方法があります。

  • ルートに基づくタブ — / に最初のタブをレンダリングする明示的なブランチを与え、マッチしない / ベースパスがそこに解決されるようにします。

    const TABS = [
      { path: "/", label: "Overview", render: () => <Overview /> },
      { path: "/customers", label: "Customers", render: () => <Customers /> },
      { path: "/reports", label: "Reports", render: () => <Reports /> },
    ];
    
    function Page() {
      const { pathname } = useDataAppLocation();
      // Exact-or-prefix match; fall back to the FIRST tab so `/` (and any
      // unknown sub-path) always shows the default tab, never a blank page.
      const active =
        TABS.find((t) => t.path !== "/" && pathname.startsWith(t.path)) ?? TABS[0];
      return active.render();
    }
    
  • ローカル状態のタブ(ルーティングなし) — アクティブタブの状態を最初のタブに初期化し、最初のレンダリングでそれが表示されるようにします。

    const [active, setActive] = useState(TABS[0].id); // default = leftmost tab
    

ベースパス(/)でアプリを新しくリロードして確認します。左端のタブのコンテンツがすぐに表示され、そのタブがタブバーで選択されていると読み取れる必要があります。

ナビゲーションが親 URL にどのように反映されるか

これについて何かをする必要はありません。参考までに:

  • バンドルが <DataAppLink to="/customers/42"> を呼び出す → ホストの navigate が /embed/apps/<name>/customers/42 で pushState を実行します。
  • 親の AppView が iframe の URL 変更を監視し、親の URL バーに /apps/<name>/customers/42 として反映します。/embed プレフィックスが取り除かれるのは、親の React Router のルートが /embed/... ではなく /apps/:name/* であるためです。
  • リロードが機能するのは、BE がすべての /embed/apps/:name/* サブパスに対して同じ data-app.html を配信するためです。iframe が起動し、<DataAppRouter> が window.location.pathname を読み取り、プレフィックスを自動検出して、正しいサブパスから開始します。

やってはいけないこと

  • プロジェクトに react-router-dom(または任意のルータライブラリ)を追加しないでください。 バンドルには必要ありません。
  • バンドル内で <BrowserRouter> や <HashRouter> を使用しないでください。 これらは独自の setState フローを実行し、Near Membrane のバッチ処理バグに遭遇します。
  • バンドル内から window.history.pushState を直接呼び出さないでください。 <DataAppLink> または useDataAppLocation の navigate を使用してください。
  • 親の URL を読み書きしようとしないでください。 iframe の sandbox 属性が設計上それをブロックします。

一般的なパターン

パスに基づく条件付きレンダリング
const { pathname } = useDataAppLocation();
if (pathname === "/") return <Home />;
if (pathname.startsWith("/customers/")) return <Customer />;
return <NotFound />;
プログラムによるナビゲーション(例: <button> から)
function OpenCustomerButton({ id }: { id: number }) {
  const { navigate } = useDataAppLocation();
  return <button onClick={() => navigate(`/customers/${id}`)}>Open #{id}</button>;
}
パスパラメータを手動で解析する

組み込みの useParams はありません。自分でマッチングしてください:

const { pathname } = useDataAppLocation();
const match = pathname.match(/^\/customers\/(\d+)$/);
if (match) {
  const id = Number(match[1]);
  // ...
}

これを頻繁に行う場合は、バンドル内の独自のヘルパーに切り出してください:

function useCustomerIdFromPath(): number | null {
  const { pathname } = useDataAppLocation();
  const m = pathname.match(/^\/customers\/(\d+)$/);
  return m ? Number(m[1]) : null;
}

トラブルシューティング

症状修正
初回読み込み時に <DataAppLink> がプレーンテキスト(クリック可能なリンクではない)としてレンダリングされる。バンドルの読み込みが完了しておらず、フォールバックパスが表示されています。プロバイダのラップは自動的に提供されます(SDK 開発プレビューの DataAppDevProvider、またはホストの DataAppProvider)。アプリ内に <MetabaseProvider> を追加しないでください。バンドルが起動すると、フォールバックは実際のリンクに解決されます。
URL は変わるが UI が変わらない。<BrowserRouter>/<HashRouter> がまだツリー内にあります。ルータライブラリを取り除き、代わりに <DataAppRouter> / <DataAppLink> を使用してください。Near Membrane と React 18 のバッチ処理の相互作用は、バンドル内で独自の setState を実行するすべてのルータを壊します。
<DataAppLink> を中クリックしても何も起こらない。リンクは event.button !== 0 を使用して左クリック以外をスキップします。イベントを飲み込む別のコンポーネントでラップしていないか確認してください。

コメント