SSGOI

React

Use SSGOI with React independently of your router. Router helpers are optional conveniences that connect a pathname and a page lifetime to the same React implementation.

Common setup

Install @ssgoi/react. Keep one provider and a stable config above routed content. The surrounding layout supplies position: relative, z-index: 0, min-height: 100dvh, and overflow-x: clip.

import type { ReactNode } from "react";
import { Ssgoi, type SsgoiConfig } from "@ssgoi/react";
import { drill } from "@ssgoi/react/view-transitions";

const config: SsgoiConfig = {
  transitions: [{ on: "/**", except: "/", transition: drill() }],
};

export function SsgoiProvider({ children }: { children: ReactNode }) {
  return <Ssgoi config={config}>{children}</Ssgoi>;
}

Using another router

A dedicated helper is not required. Connect your router’s committed pathname to a keyed DOM boundary in the same render as its children. The Route boundaries guide explains the general pattern.

Next.js

Use the App Router helper to read the committed pathname, handle URL-hook suspension, and mark the routed region. It is included in @ssgoi/react.

Connect the App Router

Place the common provider and config in app/ssgoi-provider.tsx, add "use client", and export it as SsgoiProvider. Import the ready-made boundary in your layout; no local router wrapper is needed.

import { SsgoiRouteBoundary } from "@ssgoi/react/nextjs";

// Inside your provider, around the layout's changing children:
<SsgoiRouteBoundary>{children}</SsgoiRouteBoundary>

3. Root layout

Place both pieces inside the layout shell. The wrapper supplies the containing block, stacking context, and horizontal clipping needed by the leaving page.

// app/layout.tsx
import { type ReactNode } from "react";
import { SsgoiProvider } from "./ssgoi-provider";
import { SsgoiRouteBoundary } from "@ssgoi/react/nextjs";

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <main className="relative z-0 min-h-dvh overflow-x-clip">
          <SsgoiProvider>
            <SsgoiRouteBoundary>{children}</SsgoiRouteBoundary>
          </SsgoiProvider>
        </main>
      </body>
    </html>
  );
}

Persistent layouts

A layout-owned routeKey preserves the header while the inner boundary replaces the changing content. Keep the provider above these section layouts, without a full-path boundary remounting every section from above.

// app/products/layout.tsx
import type { ReactNode } from "react";
import { SsgoiRouteBoundary } from "@ssgoi/react/nextjs";

export default function ProductsLayout({ children }: { children: ReactNode }) {
  return (
    <SsgoiRouteBoundary routeKey="products-layout">
      <header>Products</header>
      <SsgoiRouteBoundary>{children}</SsgoiRouteBoundary>
    </SsgoiRouteBoundary>
  );
}

Parallel slots and interception

Use resolve({ pathname, selectedSegments }) for slot-specific ids and keys. Function props belong in a client component. selectedSegmentsToPath(selectedSegments, basePath) resolves the owned slot beneath its layout; an empty slot is its index route, even when the browser URL points at a modal. Interception also needs the app’s route/slot files and compatible middleware or proxy rules. Verify soft navigation, back, and direct entry; the boundary includes URL-hook Suspense and accepts a fallback for Cache Components.

Remix

Experimental API

Use the Remix 2 entry with @remix-run/react. It reads Remix’s own router context, so it does not depend on a separately installed React Router version.

Wrap a layout outlet

Keep the common provider in root.tsx and place this boundary in a route layout. Follow Remix 2’s own React 18 dependency requirements. React Router 7 framework mode uses the React Router helper instead.

import { Outlet } from "@remix-run/react";
import { SsgoiRouteBoundary } from "@ssgoi/react/remix";

export default function Layout() {
  return <SsgoiRouteBoundary><Outlet /></SsgoiRouteBoundary>;
}

Persistent route families

Use routeKey for a persistent shell and a separate inner boundary for changing content. resolve receives Remix’s location and returns { id, key? }; keys are resolved during render.

React Router

Experimental API

Use the helper inside React Router 6 or 7. It follows the committed location; query-only navigation preserves the boundary.

Wrap the outlet

Put this pathless layout inside the common provider. Register the layout around the pages that should transition.

import { Outlet } from "react-router";
import { SsgoiRouteBoundary } from "@ssgoi/react/react-router";

export default function PageBoundaryLayout() {
  return <SsgoiRouteBoundary><Outlet /></SsgoiRouteBoundary>;
}

3. Route registration

In framework mode, put existing page routes under the boundary layout. Keep the document Layout and other root exports from your React Router app.

// app/routes.ts
import { type RouteConfig, index, layout, route } from "@react-router/dev/routes";

export default [
  layout("routes/page-boundary.layout.tsx", [
    index("routes/home.tsx"),
    route("posts/:postId", "routes/posts.$postId.tsx"),
  ]),
] satisfies RouteConfig;

Beyond the basics

A persistent shell needs two lifetimes: a stable outer key around the header/tabs and a pathname-keyed inner boundary around <Outlet />. Holding one boundary constant preserves the shell but produces no child OUT/IN. Mount that route family under its own layout instead of the global pathless page boundary; the complete pattern is in the agent guide and template.

TanStack Router

Experimental API

Use this entry for TanStack Router 1, including React applications built with TanStack Start. Both use the same router context.

Wrap the root outlet

Keep the common provider outside this boundary. For persistent sections, put boundaries in the owning route layouts instead.

import { Outlet } from "@tanstack/react-router";
import { SsgoiRouteBoundary } from "@ssgoi/react/tanstack-router";

<SsgoiRouteBoundary><Outlet /></SsgoiRouteBoundary>

Beyond the basics

For a persistent section, keep the root as one <Ssgoi><Outlet /></Ssgoi>, then let the section route own a stable outer shell boundary and a pathname-keyed inner boundary around <Outlet />. Holding only the outer boundary constant preserves state but produces no child transition.

The helpers connect your router to the common implementation. Route boundaries explains how to connect another router directly. Transitions and route rules apply across the web frameworks.