SSR / Next.js / Nuxt
TokenFlight widgets are Web Components that require the browser DOM. They must initialize in a Client Component because they access the DOM, customElements, and browser APIs like window.ethereum. The package can be safely imported in SSR environments — all browser-specific code is guarded with typeof window !== "undefined" checks.
Next.js (App Router)
Use the 'use client' directive and mount the widget in a useEffect:
'use client';
export function PaymentWidget({ wagmiConfig }) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const adapter = new WagmiWalletAdapter(wagmiConfig);
const widget = new TokenFlightWidget({
container: containerRef.current,
config: {
toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
tradeType: 'EXACT_OUTPUT',
amount: '100',
theme: 'dark',
},
walletAdapter: adapter,
callbacks: {
onSwapSuccess: (data) => console.log('Success:', data),
},
});
widget.initialize();
return () => widget.destroy();
}, [wagmiConfig]);
return <div ref={containerRef} style={{ minHeight: 560 }} />;
}Then use it in any page or layout:
// app/page.tsx
export default function Page() {
return <PaymentWidget wagmiConfig={wagmiConfig} />;
}Next.js (Pages Router)
Use dynamic with ssr: false to skip server rendering entirely:
const PaymentWidget = dynamic(
() => import('../components/PaymentWidget').then((mod) => mod.PaymentWidget),
{ ssr: false }
);
export default function Page() {
return <PaymentWidget />;
}See Next.js Example for a complete setup.
Nuxt 3
Use the <ClientOnly> component:
<template>
<ClientOnly>
<PaymentWidget />
</ClientOnly>
</template>
<script setup>
</script>Or use a .client.vue suffix — Nuxt automatically skips SSR for these components:
components/
PaymentWidget.client.vue ← Only rendered on the clientSee Vue Example for the full component implementation.
Remix
Use remix-utils ClientOnly or lazy-load in a useEffect:
export default function PaymentPage() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let widget: { destroy(): void } | undefined;
import('@tokenflight/swap').then(({ TokenFlightWidget }) => {
if (!containerRef.current) return;
widget = new TokenFlightWidget({
container: containerRef.current,
config: {
toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
tradeType: 'EXACT_OUTPUT',
amount: '100',
theme: 'dark',
},
});
widget.initialize();
});
return () => widget?.destroy();
}, []);
return <div ref={containerRef} style={{ minHeight: 560 }} />;
}Astro
Astro <script> tags run on the client by default — no special handling needed:
---
// This runs on the server — no browser APIs here
---
<div id="widget" style="min-height: 560px;"></div>
<script>
import { TokenFlightWidget } from '@tokenflight/swap';
import { registerWidgetElement } from '@tokenflight/swap/widget';
registerWidgetElement();
const widget = new TokenFlightWidget({
container: '#widget',
config: {
toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
tradeType: 'EXACT_OUTPUT',
amount: '100',
theme: 'dark',
},
});
widget.initialize();
</script>SvelteKit
Use onMount which only runs on the client:
<script>
import { onMount } from 'svelte';
let container;
onMount(async () => {
const { TokenFlightWidget } = await import('@tokenflight/swap');
const { registerWidgetElement } = await import('@tokenflight/swap/widget');
registerWidgetElement();
const widget = new TokenFlightWidget({
container,
config: {
toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
tradeType: 'EXACT_OUTPUT',
amount: '100',
theme: 'dark',
},
});
widget.initialize();
return () => widget.destroy();
});
</script>
<div bind:this={container} style="min-height: 560px;"></div>Static Site Generation (SSG)
SSG works with all the above patterns. The widget simply hydrates on the client after the static HTML loads. No additional configuration needed.
Key Points
- Safe to import on the server — no side effects until
windowis available - Always mount in a client-side lifecycle hook —
useEffect,onMount,onMounted, etc. - Call
destroy()in cleanup — prevents memory leaks on navigation - Cache adapter instances — avoid recreating on every render (use
useMemo,ref, etc.)