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)
Section titled “Next.js (App Router)”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:
import { PaymentWidget } from './PaymentWidget';
export default function Page() { return <PaymentWidget wagmiConfig={wagmiConfig} />;}Next.js (Pages Router)
Section titled “Next.js (Pages Router)”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.
Nuxt 3
Section titled “Nuxt 3”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 clientSee 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>SvelteKit
Section titled “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)
Section titled “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
Section titled “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.)