> ## Documentation Index
> Fetch the complete documentation index at: https://www.velora.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Velora Widget compatibility

> Tested environments, peer-dependency ranges, server-side rendering support, and performance tips for @velora-dex/widget.

## Peer dependencies

| Package | Version |
| - | - |
| `react` | `18.x` or `19.x` |
| `react-dom` | `18.x` or `19.x` |
| `@tanstack/react-query` | `^5.90` |

`@tanstack/react-query` only needs to be installed. The widget manages its own query client internally.

## Tested frameworks

| Framework | Status | Notes |
| - | - | - |
| Vite + React | ✅ Works out of the box | See [examples/react](/docs/widget/examples/react). |
| Next.js (app router) | ✅ Works, server-rendered | Render it from a `"use client"` module. See [examples/nextjs](/docs/widget/examples/nextjs). |
| Next.js (pages router) | ✅ Works | Client-only via `dynamic(... { ssr: false })`, or server-rendered with `ssrState` from `getServerSideProps`. |
| Astro | ✅ Works, server-rendered | Needs a server adapter and `prerender = false` on the route. |
| TanStack Start | ✅ Works, server-rendered | Resolve in a route loader, including React Server Components. |
| Create React App | ✅ Works | No special setup. |

## Server-side rendering

The widget server-renders. Render `<Widget />` from a server-rendered route and the HTML contains a working widget, painted with its **default state**: the default pair for the configured chain, empty amounts, the default trade mode. The client resolves the tokens your `input` names shortly after mount, which shows up as a brief flash.

To remove that flash, resolve the tokens on the server with the `@velora-dex/widget/ssr` entry and pass the result back in through `ssrState`:

```tsx theme={null}
// server
import { FileCache, resolveWidgetInitState } from "@velora-dex/widget/ssr";

const { initState } = await resolveWidgetInitState(
  { config: widgetConfig, input },
  { cache: new FileCache() }
);

// client, same config object, same initState object
<Widget config={widgetConfig} input={input} ssrState={{ initState }} />;
```

`resolveWidgetSSRQueries` takes it one step further and prefetches prices and bridge routes, so rates are on the page at first paint too.

Keep the `/ssr` entry out of browser bundles: `FileCache` imports `node:fs`, so value imports belong in server-only modules. Type imports are erased at compile time and are safe anywhere.

Call `resolveWidgetInitState` with the same `config` object you render `<Widget />` with. It decides which token lists and trade modes are enabled, and the two renders disagree if it drifts.

The route has to render per request, since the resolved state depends on the request. A statically prerendered page cannot carry it.

[Server-side rendering](/docs/widget/ssr) has the rest: prefetching rates with `resolveWidgetSSRQueries`, picking a cache backend for your runtime, acting on a token that could not be resolved, and deferring the widget behind a placeholder.

### Client-only mount

Still supported, and the right answer when there is no server at request time (a static export) or you want the widget's JavaScript off the critical path:

```tsx theme={null}
// Next.js app router
import dynamic from "next/dynamic";
import { WidgetSkeleton } from "@velora-dex/widget";

const Widget = dynamic(
  () => import("@velora-dex/widget").then((m) => ({ default: m.Widget })),
  { ssr: false, loading: () => <WidgetSkeleton theme="light" /> }
);
```

The initial HTML then has no widget in it, so anything that doesn't run JavaScript sees only the placeholder.

## Tailwind v4

The widget ships compiled Tailwind v4 styles scoped under the `.velora-widget` class. You don't need Tailwind in your host app; the bundled styles are self-contained.

If your host app **does** use Tailwind, the widget's scoping prevents its variables from leaking into your global stylesheet, and vice versa. See [Customize](/docs/widget/customize#css-scoping).

## Stylesheet auto-injection

The compiled stylesheet is auto-injected via `vite-plugin-lib-inject-css` when you import `Widget`. No explicit `.css` import is normally needed.

If your bundler strips side-effect imports aggressively (some custom Webpack / Rollup setups), force-include the stylesheet:

```tsx theme={null}
import "@velora-dex/widget/styles.css";
```

## Error boundaries

Wrap the widget in an error boundary for production. A widget crash shouldn't take down your whole page.

```tsx theme={null}
import { Component, type ReactNode } from "react";
import { Widget } from "@velora-dex/widget";

class WidgetErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> {
  state = { hasError: false };
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  render() {
    if (this.state.hasError) {
      return <div>The trading widget hit an error. Please refresh.</div>;
    }
    return this.props.children;
  }
}

export function SafeWidget() {
  return (
    <WidgetErrorBoundary>
      <Widget />
    </WidgetErrorBoundary>
  );
}
```

## Performance tips

### Memoize non-primitive config

Passing inline arrays or objects forces a re-render. Memoize them.

```tsx theme={null}
import { useMemo } from "react";
import { Widget } from "@velora-dex/widget";

function MyWidget({ theme }) {
  const config = useMemo(
    () => ({
      theme,
      tradeModes: ["swap", "limit"],
      partnerConfig: { partner: "my-app-name" },
    }),
    [theme]
  );

  return <Widget config={config} />;
}
```

### Lazy-load when offscreen

If the widget isn't above the fold, defer it.

```tsx theme={null}
import { lazy, Suspense } from "react";

const Widget = lazy(() =>
  import("@velora-dex/widget").then((m) => ({ default: m.Widget }))
);

export function DeferredWidget() {
  return (
    <Suspense fallback={<div>Loading…</div>}>
      <Widget />
    </Suspense>
  );
}
```

### Bundle size

The widget is large: it ships wallet connectors, EVM tooling, and the full trading UI. Lazy-loading and route-based code splitting keep the initial bundle small.

## Known caveats

* Strict-mode double-mount in dev triggers a brief double-fetch of price quotes, harmless and confined to development.
* Sandboxed iframes without `allow-popups` and `allow-storage-access-by-user-activation` will break wallet-connection popups.
* Don't server-render two widgets with different `input` on one page. Each resolves its own state on the server, but they share one store in the browser, so the second adopts the first's trade and React reports a hydration mismatch. Two widgets showing the same trade are fine.
* The `FileCache` disk tier degrades quietly on a read-only filesystem: every write is best-effort, so you get no caching and no error. Serverless targets usually want `Cache`, or a backend of your own.

## Related pages

* [Install](/docs/widget/install) — install + render.
* [examples/react](/docs/widget/examples/react), [examples/nextjs](/docs/widget/examples/nextjs) — concrete setups.
* [Customize](/docs/widget/customize) — CSS scoping details.
* [Server-side rendering](/docs/widget/ssr) — resolving state on the server, caching, deferred rendering.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.