Next.js
App router + static export. The two pitfalls to know up front, plus a working pattern.
Use Next.js when you want file-based routing, SSG, React server components, or any ecosystem integration. Two pitfalls to know up front:
- The host bridge lives on
window. It does not exist during SSR. Any code that reads it must run in a Client Component insideuseEffect/componentDidMount. - For publishing, you want a fully static output (
output: 'export'). The host doesn't run a Next.js server — your mini-app is shipped as bytes to the CDN.
Install
pnpm add @i99dash/sdk @i99dash/sdk-react
pnpm add -D @i99dash/sdk-cli@i99dash/sdk-react is optional but pulls its weight: one provider
- one hook per family, with a
fallbackthat handles SSR / no-host out of the box. The plain SDK example below also works.
Static export
next.config.mjs:
export default {
output: 'export',
trailingSlash: true,
images: { unoptimized: true },
};sdk.config.json
{
"appRoot": "./out",
"mocksDir": "./mocks",
"buildCommand": "next build"
}next build with output: 'export' drops the static site in ./out;
the SDK copies that to dist/ and tarballs it.
Client-component pattern
With @i99dash/sdk-react (recommended)
Mount the provider once at the top of your client tree; every hook below picks it up.
app/Providers.client.tsx:
'use client';
import { useEffect, useState } from 'react';
import { MiniAppProvider } from '@i99dash/sdk-react';
import { createClientOrSSR, type MiniAppClient } from '@i99dash/sdk';
export function Providers({ children }: { children: React.ReactNode }) {
// SSR-safe: returns null on the server, real client on the browser.
const [client, setClient] = useState<MiniAppClient | null>(() => createClientOrSSR());
useEffect(() => setClient(createClientOrSSR()), []);
return <MiniAppProvider client={client}>{children}</MiniAppProvider>;
}app/layout.tsx:
import { Providers } from './Providers.client';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}app/fuel/Fuel.client.tsx:
'use client';
import { useMiniAppContext } from '@i99dash/sdk-react';
export default function Fuel() {
const { data: ctx, loading } = useMiniAppContext({
fallback: { locale: 'en', isDark: false } as any,
});
if (loading) return <p>loading…</p>;
return <pre>{JSON.stringify(ctx, null, 2)}</pre>;
}Without sdk-react (plain SDK)
app/fuel/Fuel.client.tsx:
'use client';
import { useEffect, useState } from 'react';
import { MiniAppClient, type MiniAppContext } from '@i99dash/sdk';
export default function Fuel() {
const [ctx, setCtx] = useState<MiniAppContext | null>(null);
useEffect(() => {
const client = MiniAppClient.fromWindow();
client
.getContext()
.then(setCtx)
.catch((e) => console.error(e));
}, []);
if (!ctx) return <p>loading…</p>;
return <pre>{JSON.stringify(ctx, null, 2)}</pre>;
}app/fuel/page.tsx:
import Fuel from './Fuel.client';
export default function Page() {
return <Fuel />;
}Running both dev servers
# terminal 1 — Next.js
pnpm next dev --port 3000
# terminal 2 — sdk-dev-server pointing at a static build
pnpm next build && pnpm next export
sdk-i99dash dev --port 5173Or inline the shim during dev: add
<script src="http://127.0.0.1:5173/_sdk/bridge.js"> to your root
layout behind a process.env.NODE_ENV === 'development' check.
Working example
A complete Next.js 15 app-router project — including driving-state
banner, RTL flip on Arabic context, and callApi against a local
fixture — lives at
examples/nextjs-example/
in the SDK repo. Clone, pnpm install, pnpm dev.
Common gotchas
| Symptom | Fix |
|---|---|
window is not defined build error | You called MiniAppClient.fromWindow() outside useEffect. Wrap it. |
next build errors with "page is not exportable" | A Server Component imports the SDK runtime. Move the import into a 'use client' file. |
| Hydration mismatch between server-rendered and client | You're rendering context-derived markup before useEffect runs. Render a stable placeholder (null or <p>loading…</p>) on first paint. |
Missing host bridge in dev when running next dev | Use the inline shim trick above, or run sdk-i99dash dev against the static export instead. |
Related
- Best practices — including SSR/CSR splits.
- Local development — fixture grammar.
- Type-only imports — for SSR pages
that only need the shape of
MiniAppContext.