Routing
The file system is the router.
Every .alpine file under pages/ is a route. The path on disk becomes
the URL — with index collapsing to the parent and [param] segments
matching dynamically.
The mapping #
scanPages walks pages/**/*.alpine and turns each file into a route.
A trailing index is dropped, and [name] becomes a
:name parameter:
| File | URL | Kind |
|---|---|---|
pages/index.alpine | / | static |
pages/about.alpine | /about | static |
pages/blog/index.alpine | /blog | static |
pages/blog/[slug].alpine | /blog/:slug | dynamic |
Nesting works to any depth — folders become path segments. Generate a page with
apex make page blog/hello.
Index routes #
index.alpine represents the folder it sits in. pages/index.alpine is
the site root /; pages/blog/index.alpine is /blog.
Dynamic [param] segments #
Wrap a segment in square brackets to capture it. The matched value is passed to the page's
loader() as params:
pages/blog/[slug].alpine
<script server lang="ts">
export function loader({ params }: { params: Record<string, string> }) {
return { slug: params.slug }
}
</script>
<template x-data>
<main>
<h1 x-text="'Post: ' + slug"></h1>
<a href="/">← Home</a>
</main>
</template>
Requesting /blog/hello-world calls the loader with
params.slug === "hello-world". Segment values are URL-decoded for you.
apex dev and apex start render dynamic routes per request. A plain
apex build / apex build --islands prerenders only static
routes and reports the dynamic ones it skipped — build with --server to serve
them in production. See Build & deploy.
Precedence #
Routes are matched by segment count and then literal-vs-param. Static routes take
precedence over dynamic ones, so pages/blog/new.alpine
(/blog/new) wins over pages/blog/[slug].alpine for the exact path
/blog/new.
404s #
A request that matches no route returns a 404. Keep URLs relative in your own links (Apex is happy under a sub-path), and let unmatched paths fall through to the framework's not-found response.
Client-side navigation #
Links between your pages are upgraded to in-place navigation: Apex fetches
the target page, swaps the content, merges its <head> (title + meta), and
re-runs Alpine — with no full reload, so Alpine, stores, and the runtime stay
alive between pages. It's on by default; there's nothing to wire up.
- Prefetch — pages are fetched on link hover / viewport entry, so the click is instant.
- Progress bar — a thin bar animates across the top during a navigation.
- History & scroll — back/forward work, and scroll position is restored.
Add a pages/loading.alpine and Apex shows it in the page region during a slow
navigation (a slow loader()) — a loading boundary.
Opt out where you want a real browser navigation:
<!-- full page load, no swap -->
<a href="/download" data-apex-no-nav>Download</a>
<!-- navigated normally, but never prefetched -->
<a href="/report" data-apex-no-prefetch>Heavy report</a>
Cross-origin links, downloads, and modified clicks (⌘/Ctrl-click, new tab) always fall back to
a normal load. To disable the whole feature, set clientNav: false
in apex.config.ts.
Client-side navigation applies to the standard SSR/hydration build. Pages built with
apex build --islands navigate with full page loads.