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/:slugConfiguration
// 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/* pagesLoading States
Pages support _loading.jsx for loading UI:
pages/
_loading.jsx <- shown while page is loadingPage 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:
| Return | Effect |
|---|---|
{ 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.