Arcwayv0.3.0

Pages (SSR)

Server-side rendered React pages, layouts, middleware, and client hooks

Arcway supports server-side rendered (SSR) pages using React. Pages use file-based routing in a pages/ directory at the project root.

File Structure

pages/
├── index.jsx                    # /
├── about.jsx                    # /about
└── blog/
    ├── index.jsx                # /blog
    └── [slug].jsx               # /blog/:slug

Configuration

// arcway.config.js
export default {
  pages: {
    enabled: true, // Default: true
  },
};

Pages are built automatically during dev and production startup. Built bundles go to .build/pages/.

Page Components

Pages export a default React component. Data fetching is done client-side with useApi:

// pages/blog/[slug].jsx
import { useApi, useRouter } from 'arcway/lib/client';

export default function BlogPost() {
  const { slug } = useRouter().params;
  const { data, loading, error } = useApi(`/posts?slug=${slug}`);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <article>
      <h1>{data.title}</h1>
      <div>{data.content}</div>
    </article>
  );
}

Layouts

Pages support _layout.jsx files for nested layout chains:

pages/
  _layout.jsx          <- wraps ALL pages
  index.jsx
  dashboard/
    _layout.jsx        <- wraps dashboard/* pages (nested inside root layout)
    index.jsx
    settings.jsx
// pages/_layout.jsx
export default function RootLayout({ children }) {
  return (
    <div className="app-shell">
      <nav>...</nav>
      <main>{children}</main>
    </div>
  );
}

Layouts cascade from parent to child -- a page gets wrapped by all layouts in its ancestor directories.

Error Pages

Pages support _error.jsx for custom error rendering:

pages/
  _error.jsx           <- catches errors for all pages
  dashboard/
    _error.jsx         <- catches errors only for dashboard/* pages

Loading States

Pages support _loading.jsx for loading UI:

pages/
  _loading.jsx         <- shown while page is loading

Page Middleware

Pages support server-side _middleware.js:

// pages/_middleware.js
export default async (ctx) => {
  // ctx.page has: pathname, query, headers, cookies, session
  // ctx also has: db, log, events, cache, queue, files, mail
  if (!ctx.page.session?.userId) {
    return { redirect: '/login' };
  }
  // return void to continue
};

Page middleware runs server-side before SSR rendering. It can redirect, block with a status code, or pass through.

The middleware context includes:

{
  page: {
    pathname,  // e.g. '/blog/hello-world'
    query,     // path parameters
    headers,   // request headers
    cookies,   // parsed cookies
    session,   // unsealed session data
  },
  db, log, events, cache, queue, files, mail, // infrastructure
}

Return values:

ReturnEffect
{ redirect: '/login' }302 redirect (or custom status)
{ redirect: '/login', status: 301 }Redirect with custom status
{ status: 403 }Block with status code
{ status: 403, body: '<h1>Forbidden</h1>' }Block with custom HTML body
undefined (return nothing)Continue to render the page

Per-Page Code Splitting

Arcway automatically splits your client-side JavaScript so each route only loads the chunks it actually needs.

How it works

During a production build, Arcway traces the transitive import graph for every page entry point using esbuild's metafile. Only the chunks that a given route actually imports (directly or transitively) are listed in that route's manifest entry and emitted as <script> tags when the page is served.

For example, if /ai/files imports CodeMirror but /ai/inbox does not, the CodeMirror chunk appears in /ai/files HTML and never in /ai/inbox HTML.

Manifest shape

Each route's manifest entry has a sharedChunks array with the chunks it needs:

{
  "entries": [
    {
      "pattern": "/ai/inbox",
      "clientBundle": "client/_ai_inbox-XYZ.js",
      "sharedChunks": [
        "client/chunks/react-ABC.js",
        "client/chunks/router-DEF.js"
      ]
    },
    {
      "pattern": "/ai/files",
      "clientBundle": "client/_ai_files-UVW.js",
      "sharedChunks": [
        "client/chunks/react-ABC.js",
        "client/chunks/router-DEF.js",
        "client/chunks/codemirror-GHI.js"  // only here
      ]
    }
  ]
}

The SSR renderer emits a <script type="module"> tag for each chunk in sharedChunks followed by the page's own entry bundle. No global chunk list exists — every route knows exactly what it needs.

Deferring heavy components

For components only needed on interaction (code editors, charts, maps), use arcway/dynamic to exclude them from the initial page load entirely. See Dynamic Imports.

Client-Side Navigation

Arcway provides client-side hooks for SPA-like navigation:

import { Link, useRouter } from 'arcway/lib/client';

function Nav() {
  const pathname = useRouter().pathname;
  return (
    <nav>
      <Link href="/" className={pathname === '/' ? 'active' : ''}>
        Home
      </Link>
      <Link href="/about">About</Link>
    </nav>
  );
}

The Link component supports prefetching:

<Link href="/page" prefetch="hover">Prefetch on hover (default)</Link>
<Link href="/page" prefetch="viewport">Prefetch when visible</Link>
<Link href="/page" prefetch="none">No prefetching</Link>
<Link href="/page" replace>Replace history entry</Link>

For the full client-side hooks and components reference (useApi, useMutation, Provider, etc.), see the Client Reference.

On this page