Skip to content

docs: document SSR / Hydration guidelines for Meta-Frameworks (TanStack Start, Remix, Nuxt) #498

Open

Description

@roblevintennis

Proposed Documentation Updates

Note this is related to #500

Part 1: Redirecting the Existing FAQ Entry

The current FAQ entry will be made brief and direct readers straight to the comprehensive integration blueprint to ensure a clean navigation experience.

Target File: site/docs/faq.md

### Can I use AgnosticUI with SSR / SSG meta-frameworks?

Yes, but because AgnosticUI leverages highly optimized Lit-based Web Components, their browser-native lifecycle requires consideration inside server-rendered environments to prevent hydration errors. 

For concrete setup guides and code blueprints covering TanStack Start, Remix, and Nuxt, see our dedicated [SSR & Meta-Frameworks Integration Guide](/docs/ssr.html).

Part 2: Documentation Tree Hierarchy Placement

The new guide will live directly beneath the existing installation sub-sections for visibility.

Target Path: site/docs/ssr.md

Sidebar Navigation
├── Quick Start
│   ├── Installation
│   │   ├── ...
│   │   └── Framework Support
│   │   └── SSR & Meta-Frameworks (Add New Route Link Here)

Part 3: Dedicated SSR Guide Content

This copy replaces the initial draft with clean layout snippets and incorporates a dedicated section on value tracking and controlled states within isolated environments.

Target File: site/docs/ssr.md

# Server-Side Rendering (SSR) & Meta-Frameworks

Using AgnosticUI within server-rendered frameworks requires client-only isolation to prevent structural hydration mismatches caused by browser-native Lit Web Components initializing before hydration completes.

## The Solution: Client-Only Isolation
Isolate components to initialize exclusively on the client side after the browser mounts. Refer to the framework-specific blocks below to safely handle boundaries:

### 🚀 TanStack Start
Wrap your AgnosticUI components using TanStack's built-in `<ClientOnly>` component, providing a lightweight skeleton placeholder as a fallback:

```tsx
import { ClientOnly } from "@tanstack/react-router";
import { ReactSelect } from "./components/ag/Select/react/ReactSelect";

export default function MyComponent() {
  return (
    <ClientOnly fallback={<div className="animate-pulse bg-slate-200 h-9 rounded" />}>
      <ReactSelect label="Options" name="my-select">
        <option value="1">Option 1</option>
      </ReactSelect>
    </ClientOnly>
  );
}

💿 Remix / React Router v7

Use the community standard <ClientOnly> wrapper from remix-utils/client-only:

import { ClientOnly } from "remix-utils/client-only";
import { ReactSelect } from "./components/ag/Select/react/ReactSelect";

export default function MyComponent() {
  return (
    <ClientOnly fallback={<div>Loading...</div>}>
      {() => (
        <ReactSelect label="Options" name="my-select">
          <option value="1">Option 1</option>
        </ReactSelect>
      )}
    </ClientOnly>
  );
}

🟢 Nuxt (Vue 3)

Vue and Nuxt feature native support for client-side isolation using the built-in <ClientOnly> tag:

<template>
  <ClientOnly>
    <VueSelect label="Options" name="my-select">
      <option value="1">Option 1</option>
    </VueSelect>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </ClientOnly>
</template>

Controlled State & Value Management in ReactSelect

Because ReactSelect compiles down to a custom Web Component container generated by @lit/react, it does not feature a standard React-controlled value property on its wrapper.

If you are piping external route query parameters or state variables directly down into the selector dropdown, you must manage selections declaratively via the child <option> tags using React's native selected property inside your <ClientOnly> wrapper.

<ClientOnly fallback={<div className="h-[38px] bg-slate-100 animate-pulse rounded" />}>
  <ReactSelect
    label="Filter Categories"
    labelHidden={true}
    name="categoryFilter"
    onChange={(e: any) => handleFilterChange(e.detail.value)}
  >
    <option value="" selected={currentFilter === ""}>
      All Categories
    </option>
    {categories.map((cat) => (
      <option 
        key={cat.id} 
        value={cat.name}
        selected={currentFilter === cat.name}
      >
        {cat.name}
      </option>
    ))}
  </ReactSelect>
</ClientOnly>

Best Practices for Your Project

  • SPAs & Dashboards: If your application sits behind an authentication wall or acts as a Single Page App dashboard where SEO is not a priority, you can often disable SSR globally or per-route within your framework config to bypass hydration guards entirely.
  • Public Pages & SEO: For high-traffic, public-facing landing pages where search engine indexing is critical, we recommend utilizing native semantic HTML elements for basic inputs (buttons, text fields) and reserving AgnosticUI for complex interactive components on the page.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions