Skip to content
TokenFlight SDK

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.

Use the 'use client' directive and mount the widget in a useEffect:

'use client';
import { useEffect, useRef } from 'react';
import { TokenFlightWidget } from '@tokenflight/swap';
import { WagmiWalletAdapter } from '@tokenflight/adapter-wagmi';
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
import { PaymentWidget } from './PaymentWidget';
export default function Page() {
return <PaymentWidget wagmiConfig={wagmiConfig} />;
}

Use dynamic with ssr: false to skip server rendering entirely:

import dynamic from 'next/dynamic';
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.

Use the <ClientOnly> component:

<template>
<ClientOnly>
<PaymentWidget />
</ClientOnly>
</template>
<script setup>
import PaymentWidget from '~/components/PaymentWidget.vue';
</script>

Or use a .client.vue suffix — Nuxt automatically skips SSR for these components:

components/
PaymentWidget.client.vue ← Only rendered on the client

See Vue Example for the full component implementation.

Use remix-utils ClientOnly or lazy-load in a useEffect:

import { useEffect, useRef } from 'react';
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 <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>

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>

SSG works with all the above patterns. The widget simply hydrates on the client after the static HTML loads. No additional configuration needed.

  • Safe to import on the server — no side effects until window is available
  • Always mount in a client-side lifecycle hookuseEffect, onMount, onMounted, etc.
  • Call destroy() in cleanup — prevents memory leaks on navigation
  • Cache adapter instances — avoid recreating on every render (use useMemo, ref, etc.)