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.