Loading...
Loading...
Add client-side routing (multiple pages) to an existing Metabase data-app project using the host-provided `DataAppRouter`, `DataAppLink`, and `useDataAppLocation` primitives. Use when the user has an existing data-app project and wants more than one page.
npx skill4agent add metabase/metabase metabase-data-app-routing@metabase/embedding-sdk-react/data-app| API | Purpose |
|---|---|
| Wrap the app once. Tracks the current sub-path. Auto-detects the iframe's URL prefix; bundle author writes no basename. |
| Internal navigation link. Renders a real |
| Returns |
react-router<BrowserRouter><HashRouter>data-app-templatevite.config.tsdataAppConfig()src/index.tsx/overview/customers/:id@metabase/embedding-sdk-react/data-appvite.config.tsvite.config.tssrc/App.tsxApp.tsx<DataAppRouter><DataAppRouter>basename/embed/apps/<name>App.tsx<MetabaseProvider>DataAppDevProviderDataAppProviderimport { 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 devhttp://localhost:5174/customers/42///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/<DataAppLink to="/customers/42">navigatepushState/embed/apps/<name>/customers/42AppView/apps/<name>/customers/42/embed/apps/:name/*/embed/...data-app.html/embed/apps/:name/*<DataAppRouter>window.location.pathnamereact-router-dom<BrowserRouter><HashRouter>setStatewindow.history.pushState<DataAppLink>navigateuseDataAppLocationconst { 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>;
}useParamsconst { 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;
}| Symptom | Fix |
|---|---|
| The bundle hasn't finished loading and the fallback path is showing. The provider wrap is supplied automatically (the SDK dev preview's |
| URL changes in dev preview but the production iframe shows the bundle re-render itself on every navigation (or routes don't work in prod at all). | |
| URL changes but UI doesn't. | A |
Reload at a deep URL in dev ( | Vite's dev server is serving the route as a 404 instead of falling back to |
Middle-click on a | The link uses |