v 0.1.63

React Router

The @ecopages/react-router package enables Single Page Application (SPA) navigation for EcoPages React applications. It provides seamless client-side navigation while preserving full SSR benefits.

Features

Installation

bun add @ecopages/react-router

Setup

1. Configure the React Plugin

Add the router adapter to your eco.config.ts:

import { ConfigBuilder } from '@ecopages/core';
import { reactPlugin } from '@ecopages/react';
import { ecoRouter } from '@ecopages/react-router';
 
const config = await new ConfigBuilder()
  .setRootDir(import.meta.dir)
  .setIntegrations([
    reactPlugin({ router: ecoRouter() }),
  ])
  .build();
 
export default config;

That's it, all pages now have SPA navigation enabled.

2. Use Layouts (Optional)

For persistent UI across navigations (headers, sidebars), use config.layout:

// src/layouts/base-layout.tsx
export const BaseLayout = ({ children }) => (
  <html>
    <head>...</head>
    <body>
      <header>My Site</header>
      <main>{children}</main>
      <footer>...</footer>
    </body>
  </html>
);
 
// src/pages/index.tsx
import { BaseLayout } from '../layouts/base-layout';
import { eco } from '@ecopages/core';
 
const HomePage = eco.page({
  layout: BaseLayout,
  render: () => <h1>Welcome</h1>
});
 
export default HomePage;

When navigating between pages with the same layout, only the page content updates - the layout stays mounted.

View Transitions

Default Behavior: The router automatically optimizes shared element transitions to "morph" the geometry without ghosting (no cross-fade).

Directives

Control the animation style with data-view-transition-animate:

ValueDescription
morph(Default) a clean geometric morph. Prevents ghosting on shared elements.
fadeStandard cross-fade animation. Use this to opt-out of the automatic fix.

Example:

<div data-view-transition="hero" data-view-transition-animate="fade">

Duration

Control the speed of a specific transition:

<div data-view-transition="hero" data-view-transition-duration="500ms">

3. Use Standard Links

// These links are automatically intercepted for SPA navigation
<a href="/about">About</a>
<a href="/blog">Blog</a>
 
// Force full page reload with data-eco-reload
<a href="/external" data-eco-reload>External Link</a>

How It Works

The router uses an HTML-First navigation strategy. This simplifies the architecture by treating the server-rendered HTML as the source of truth for both code splitting and data.

  1. Initial Load: Server renders the full page with SSR.
  2. Hydration: React hydrates the page, router starts listening for clicks.
  3. Navigation: When a link is clicked:
    • Fetch: Router fetches the target page HTML.
    • Prep: Extracts component URL and props; imports the new component.
    • Transition:
      • Captures "Old" state (Screenshot).
      • React renders "New" state (DOM update).
      • Browser animates from Old to New.
┌─────────────────────────────────────────────────────┐
│  Browser View Transition                            │
│                                                     │
│  1. Capture Old State (Screenshot)                  │
│          ↓                                          │
│  2. React State Update                              │
│     (Mount New Page Component)                      │
│          ↓                                          │
│  3. Wait for Render                                 │
│          ↓                                          │
│  4. Animate (Old → New)                             │
└─────────────────────────────────────────────────────┘

API

ecoRouter()

Factory function that creates a router adapter for the React plugin.

import { ecoRouter } from '@ecopages/react-router';
 
reactPlugin({ router: ecoRouter() })

useRouter()

Hook for programmatic navigation.

import { useRouter } from '@ecopages/react-router';
 
const MyComponent = () => {
  const { navigate, isPending } = useRouter();
 
  const handleClick = () => {
    navigate('/about');
  };
 
  return (
    <button onClick={handleClick} disabled={isPending}>
      {isPending ? 'Loading...' : 'Go to About'}
    </button>
  );
};

Router Options

The router respects these HTML attributes:

AttributeDescription
data-eco-reloadForce a full page reload instead of SPA navigation
target="_blank"Opens in new tab (not intercepted)

Links are automatically skipped when:

Architecture

The router uses a pluggable adapter pattern that separates concerns:

ReactRouterAdapter Interface:

interface ReactRouterAdapter {
  name: string;
  bundle: { importPath, outputName, externals };
  importMapKey: string;
  components: { router, pageContent };
  getRouterProps(page, props): string;
}

This allows for alternative router implementations while keeping the core integration simple.

Comparison with Browser Router

Feature@ecopages/react-router@ecopages/browser-router
FrameworkReact onlyMPA (KitaJS, Lit, vanilla)
DOM StrategyReact reconciliationmorphdom diffing
Layout PersistenceVia React component treeVia data-eco-persist
View TransitionsNot yet supportedSupported
State PreservationReact state in layoutDOM element state

License

MIT