Client Architecture
Last updated: 03/04/2026Edit this page
Trellis Docs uses Next.js App Router with a mix of server and client components. Understanding the boundary helps when building custom components or debugging hydration issues.
Server vs. client components
| Type | Directive | When to use |
|---|---|---|
| Server (default) | None needed | Static content, data fetching, no interactivity |
| Client | 'use client' at top of file | Hooks, state, event handlers, browser APIs |
What runs on the server
- Page component (
app/(docs)/[...slug]/page.tsx) — loads MDX content, extracts metadata, generates the page - MDX compilation —
next-mdx-remotecompiles MDX to React components on the server - Code highlighting — Shiki processes syntax highlighting at build time (server component)
- Content loading —
lib/content.tsreads files from the filesystem
What runs on the client
- Layout (
app/(docs)/layout.tsx) — manages sidebar collapse state - Sidebar — collapsible categories, active link tracking
- Search — Fuse.js search runs entirely in the browser
- Tabs — URL sync and tab selection state
- Image lightbox — click-to-zoom modal
- Mermaid diagrams — rendered client-side via the Mermaid library
- Code block controls — copy button and word wrap toggle
- Back-to-top button — scroll detection and smooth scroll
MDX rendering pipeline
Documentation pages go through this pipeline:
- File loading —
getDocBySlug()reads the MDX file and parses frontmatter withgray-matter - Include resolution —
@includedirectives are expanded inline - Admonition preprocessing —
:::type Titlesyntax is converted to:::type[Title]forremark-directive - MDX compilation —
MDXRemotefromnext-mdx-remotecompiles the content with:remarkGfm— GitHub Flavored Markdown (tables, strikethrough, task lists)remarkDirective+remarkCallout— admonition syntaxrehypeSlug— auto-generates heading IDsrehypeCodeMeta— processes code block meta strings (title, line highlighting)
- Component mapping — custom components from
components/docs/mdx/index.tsxreplace default HTML elements - Rendering — React renders the component tree to static HTML at build time
Hydration
Since the site is statically exported, React hydrates the pre-rendered HTML on the client. This means:
- The server-rendered HTML is visible immediately (no blank page flash)
- Client components become interactive after JavaScript loads
- The HTML structure must match between server and client renders
Common hydration issues
<div>inside<p>— MDX wraps standalone elements in<p>tags. If a component renders a<div>, it creates invalid nesting. UsecreatePortalfor overlay elements.- Date/time mismatches — avoid rendering
new Date()directly, as it differs between server and client. - Browser-only APIs — wrap
window,document, orlocalStorageaccess inuseEffector checktypeof window !== 'undefined'.
Content variables
Variables defined in config/variables.ts are passed to MDX via scope: { vars: docVariables } in the MDXRemote options. This lets you use {vars.productName} in any MDX file without importing anything.