Next.js
Live projectOpen in StackBlitz
App Router (Recommended)
Create a client component for the widget:
tsx
// components/PaymentWidget.tsx
'use client';
interface PaymentWidgetProps {
wagmiConfig: Config;
theme?: 'light' | 'dark' | 'auto';
}
export function PaymentWidget({ wagmiConfig, theme = 'dark' }: PaymentWidgetProps) {
const containerRef = useRef<HTMLDivElement>(null);
const walletAdapter = useMemo(
() => new WagmiWalletAdapter(wagmiConfig),
[wagmiConfig]
);
useEffect(() => {
if (!containerRef.current) return;
const widget = new TokenFlightWidget({
container: containerRef.current,
config: {
toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
amount: '100',
tradeType: 'EXACT_OUTPUT',
theme,
},
walletAdapter,
callbacks: {
onSwapSuccess: (data) => {
console.log('Payment completed!', data.orderId, data.txHash);
},
onSwapError: (error) => {
console.error(`[${error.code}] ${error.message}`);
},
},
});
widget.initialize();
return () => widget.destroy();
}, [walletAdapter, theme]);
return <div ref={containerRef} style={{ minHeight: 560 }} />;
}Use it in a server or client page:
tsx
// app/payment/page.tsx
export default function PaymentPage() {
return (
<main>
<h1>Payment</h1>
<PaymentWidget wagmiConfig={wagmiConfig} />
</main>
);
}wagmi Configuration
ts
// lib/wagmi.ts
export const wagmiConfig = createConfig({
chains: [mainnet, base, arbitrum],
connectors: [injected()],
transports: {
[mainnet.id]: http(),
[base.id]: http(),
[arbitrum.id]: http(),
},
});Pages Router
Use next/dynamic with ssr: false:
tsx
// pages/payment.tsx
const PaymentWidget = dynamic(
() => import('../components/PaymentWidget').then((mod) => mod.PaymentWidget),
{ ssr: false, loading: () => <div style={{ minHeight: 560 }}>Loading...</div> }
);
export default function PaymentPage() {
return (
<main>
<h1>Payment</h1>
<PaymentWidget wagmiConfig={wagmiConfig} />
</main>
);
}Without a Wallet Adapter
If your Next.js app already manages wallet connections (via RainbowKit, ConnectKit, etc.), use the widget without an adapter:
tsx
'use client';
export function PaymentWidget() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const widget = new TokenFlightWidget({
container: containerRef.current,
config: {
toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
amount: '100',
tradeType: 'EXACT_OUTPUT',
theme: 'dark',
},
callbacks: {
onConnectWallet: () => {
// Trigger your existing wallet connection UI
document.querySelector('[data-connect-wallet]')?.click();
},
onSwapSuccess: (data) => {
console.log('Payment completed:', data);
},
},
});
widget.initialize();
return () => widget.destroy();
}, []);
return <div ref={containerRef} style={{ minHeight: 560 }} />;
}Key Points
- Always use
'use client'for components that reference@tokenflight/swap - Use
useReffor the container — avoids React re-render conflicts with Shadow DOM - Cache
walletAdapterwithuseMemo— prevents reconnection on re-renders - Call
destroy()in theuseEffectcleanup — cleans up the Shadow DOM on unmount - See React Integration for more patterns and TypeScript setup