docs / Native
Native Routing
File-based and manual routing in vesk-native: layout/page conventions, dynamic params, manual routes in veskconfig.ts, Link/NavLink/Outlet, and programmatic navigation.
Vesk Native uses file-based routing by default — the folder structure under `app/` maps directly to navigation routes. Manual routes can be added in `veskconfig.ts` for cases that don't fit the filesystem.
File-based routing
Place `.vsk` files in `app/` to create routes. The convention is identical to the web framework:
| File path | Route |
|---|---|
| app/page.vsk | / (root) |
| app/about/page.vsk | /about |
| app/blog/page.vsk | /blog |
| app/blog/[slug]/page.vsk | /blog/:slug |
| app/shop/[id]/page.vsk | /shop/:id |
| app/layout.vsk | Layout wrapper (not a route) |
- `layout.vsk` is skipped as a route — it wraps child routes via `{props.children}`.
- `page.vsk` inside a folder maps to that folder's path.
- `[paramName]` folders create dynamic route segments — the param is extracted from the URL.
- Nested folders create nested paths: `app/blog/[slug]/page.vsk` → `/blog/:slug`.
Layout file
component Layout(props) {
<div class="flex-col w-full h-full">
<nav class="bg-white px-4 py-2 border-b">
<NavLink href="/">
<span class="text-base font-bold">MyApp</span>
</NavLink>
<div class="flex gap-3">
<NavLink href="/about" class="text-sm">About</NavLink>
<NavLink href="/shop" class="text-sm">Shop</NavLink>
</div>
</nav>
<main class="flex-1">
{props.children}
</main>
</div>
}
Dynamic params
Use `[paramName]` in the folder name to create a dynamic segment. Access the param value with `useParams()` in the page script:
export component BlogPost() {
const params = useParams()
const slug = params.slug
<div class="p-4">
<h1 class="text-xl font-bold">Post: {slug}</h1>
</div>
}
`useParams()` returns a `Record<string, string>` with the dynamic segment values. All values are strings.
Manual routes
For routes that don't fit the filesystem, add them to `veskconfig.ts` under `routes`. Manual routes use `component` to reference a component name defined in a `.vsk` file:
import { defineConfig } from '@vesk/native'
export default defineConfig({
// ... other config
routes: [
{ path: '/settings', component: 'SettingsPage' },
{ path: '/profile/:userId', component: 'ProfilePage' },
],
})
- Manual routes are merged with file-based routes — file-based routes take precedence for the same path.
- The `component` value is the name of a component declared in any `.vsk` file in your app.
- Dynamic segments use `:paramName` syntax — same as file-based `[paramName]` folders.
- Manual routes support `exitOnBack` to mark the route as a double-back-to-exit page.
Link and NavLink
component Nav() {
<div class="flex gap-3 p-4">
<Link href="/about">
<span class="text-blue-600">About</span>
</Link>
<NavLink href="/shop" class="text-gray-600">
Shop
</NavLink>
</div>
}
- `<Link href="/path">` — a navigation link. Tap navigates to the route.
- `<NavLink href="/path" class="...">` — a link that also receives an active class when the route matches.
- Both accept `class` for styling.
- `<Outlet />` renders the child route inside a layout — use it in `layout.vsk` where you want `{props.children}` to appear.
Programmatic navigation
In page scripts, navigate imperatively with the global helpers or the router hook:
| Function | Behavior |
|---|---|
| navigate('/path') | Push a new route onto the navigation stack. |
| goBack() | Pop the navigation stack. Returns false at the root. |
| useParams() | Return the current route's dynamic params as Record<string, string>. |
| useQuery() | Return the current route's query string as Record<string, string>. |
| useRouter() | Return the full router object: { currentPath, navigate, back, refresh }. |
export component ProfilePage() {
const params = useParams()
const query = useQuery()
const router = useRouter()
<div class="p-4">
<h1>Profile: {params.userId}</h1>
<p>Tab: {query.tab}</p>
<button onClick={() => goBack()} class="mt-4 text-blue-600">
Go back
</button>
<button onClick={() => navigate('/settings')} class="mt-2 text-blue-600">
Settings
</button>
</div>
}
Back navigation behavior
The back behavior is configured in `veskconfig.ts` under `back`:
- `mode: 'stack'` — custom stack-based navigation with double-back-to-exit.
- `mode: 'system'` — delegates to the OS back button (Android default).
- `doubleBackToExit: true` — tapping back twice within `exitDelayMs` exits the app.
- `exitRoutes` — routes where a double-back exits; interior routes always pop the stack first.
- `exitRoutes` defaults to the root page (`/`) — add `{ path: '/shop', exitOnBack: true }` in manual routes to change this.
Scroll restoration
The router automatically saves and restores scroll position per route. Each route's scroll state is stored in the `NavController` and restored when navigating back.
export component ScrollList() {
const scrollState = rememberRouteScrollState(0)
<div class="overflow-y-auto flex-1" scrollState={scrollState}>
{props.children}
</div>
}
Generated Kotlin router
The build generates a `Router.kt` with a `NavController` class that holds the navigation state as Compose `mutableStateOf` properties. Route matching strips `?query` and `#fragment` segments, matches `{param}` segments, and returns `null` for unknown routes (renders nothing).
The `navigation-native` npm package provides the JS-side types (`RouteConfig`, `RouterState`, `createRouter`) — the real navigation is the generated Kotlin `NavController` running on the device.