Guides
Client Navigation & Images
Every plain <a> tag on an X site already navigates client-side, with no router setup required. This page covers that behavior, the <Link> convenience component, the built-in remote image proxy, and the two error surfaces (dev overlay, 404 page).
Client-side navigation, by default
Every rendered page inlines a small navigation script (CLIENT_NAV_SCRIPT). It intercepts clicks on same-origin <a href> elements, fetches the destination page, and swaps the page content in place instead of doing a full browser navigation, so you get SPA-style transitions without adding a router or wrapping links in anything. It also prefetches on hover/focus and handles back/forward via popstate.
// No import needed — this already does client-side nav + hover prefetch:<a href="/docs/routing">Routing</a>Opt individual links out with data attributes:
<a href="/legacy" data-no-nav>Full page load</a><a href="/heavy-page" data-no-prefetch>No hover prefetch, still client nav</a>Links are skipped automatically if they cross origins, open a new tab (target), carry a download attribute, or use a #/mailto:/tel: scheme. Those always behave like normal anchors.
The <Link> component
<Link> is a typed wrapper around the same behavior above. Use it when you want the opt-out props to be type-checked instead of stringly-typed data attributes:
import { Link } from "@thexjs/core";<Link href="/docs">Docs</Link>// Same opt-outs, as real props:<Link href="/legacy" clientNav={false}>Full page load</Link><Link href="/heavy-page" prefetch={false}>No prefetch</Link>Remote image proxy
createImageProxyHandler mounts a GET /_x/image route that fetches an allow-listed remote image server-side and streams it back from your own origin. The browser never makes a cross-origin image request, so a strict img-src 'self' CSP (see Security) still works even with remote images. This page's own Stardance badge is proxied through it right now.
import { createImageProxyHandler } from "@thexjs/core";const imageProxy = createImageProxyHandler({ remoteHosts: ["stardance.hackclub.com"], // required allow-list — empty means requests are rejected});<img src={`/_x/image?url=${encodeURIComponent("https://stardance.hackclub.com/logo.png")}`} /> >It's a proxy, not an optimizer. No resizing or format conversion happens. Only hosts in remoteHosts are ever fetched (this is what prevents the route from becoming an open SSRF relay), only a fixed set of image content types are allowed through, and successful responses are served with a one-day, immutable Cache-Control header.
The <Image> component & srcSet
While the proxy streams a single remote URL, <Image> handles responsive images end to end. Remote sources are routed through /_x/image automatically and get a srcSet generated for them; local sources pass through untouched. Either way you avoid layout shift and keep img-src 'self'.
import { Image } from "@thexjs/core";<Image src="https://cdn.example.com/hero.jpg" width={1200} height={630} sizes="(min-width: 768px) 50vw, 100vw" alt="Product hero"/> >For a remote src whose host is in images.remoteHosts, the generated srcSet re-serves the image through the proxy at each breakpoint in SRCSET_WIDTHS. A host that isn't allow-listed 403s at request time, and warns in dev:
export const SRCSET_WIDTHS = [320, 640, 750, 828, 1080, 1200, 1920, 2048, 3840];// remote & allow-listed -> "…/_x/image?url=…&w=320 320w, …&w=640 640w, …&w=750 750w, …"// local src -> plain <img src>, no srcSet (no resize pipeline)Build that string manually with buildSrcSet(src, opts) when you need custom behavior. Beyond responsive sizing, priority sets fetchPriority="high" on LCP images (everything else lazy-loads), fill absolutely positions the image in its parent (nice for hero/card layouts, exclusive with width/height), and placeholder="blur" with blurDataURL shows a tiny base64 blur that fades out on load. It is CSS-only; no JavaScript island is required.
Dev error overlay
When a loader, page, or API route throws during x dev, renderErrorOverlay renders a full-screen overlay with the error message and stack trace, instead of a bare 500 response. It's dev-only: production builds never ship the overlay, they return a plain error response.
404 handling
Drop a src/pages/_404.tsx to customize the site-wide not-found page. If you don't provide one, X falls back to DefaultNotFound, a minimal built-in page, so every project has a sane 404 without extra setup.
export default function NotFound() { return <h1>Nothing here.</h1>;}