Pages & Routing
Avenx-JS features a built-in router designed for single-page applications. It handles hash-based navigation (e.g. #/dashboard), dynamic parameters, and guards.
1. Page Components (.page.js)
Section titled “1. Page Components (.page.js)”Pages are top-level components located inside src/pages/. They extend AvenxPage instead of AvenxComponent, enabling them to host child components dynamically.
2. Configuring the Router
Section titled “2. Configuring the Router”Define routes in your src/main.app.js file by mapping path patterns to page names:
import { AvenxApp } from 'avenx-core/runtime';const app = new AvenxApp({ target: '#app' });// Registering Pages (Normally automatically registered by compiler)app.registerPage('Home', Home);app.registerPage('Profile', Profile);// Initialize routerapp.initRouter({ '/': 'Home', '/profile/:id': 'Profile', '*': 'Home', // Fallback route});Keep-Alive Page Caching
Section titled “Keep-Alive Page Caching”Routes can enable page caching by setting the keepAlive option. Instead of destroying the page when navigating away, Avenx stores the page instance in an internal Least Recently Used (LRU) cache.
When the user returns to the same route, the cached page instance is restored instead of creating a new one. This preserves the page’s DOM state, component state, and any user input that has not been cleared.
app.initRouter({ '/profile/:id': { page: 'Profile', keepAlive: true, },});When a cached page is restored, the onActivate(params) lifecycle hook is called with the latest route parameters. When navigating away from a cached page, onDeactivate() is called instead of onUnmount().
This behavior is useful for pages that should preserve their state between navigations, such as dashboards, forms, or long lists.
keepAliveLimit
Section titled “keepAliveLimit”The maximum number of inactive keep-alive page instances is controlled by the keepAliveLimit option passed to the AvenxApp constructor.
When navigating away from a page configured with keepAlive: true:
onDeactivate()runs and the page instance is moved into the internal LRU cache.- If caching another inactive page would exceed
keepAliveLimit, the least recently used cached page is evicted. - The evicted page’s
onUnmount()lifecycle hook runs.
const app = new AvenxApp({ target: '#app', keepAliveLimit: 3,});With this configuration, navigating among four or more keep-alive pages retains only the three most recently used inactive page instances in memory. Older cached pages are automatically removed as needed.
Programmatic Page Cache Invalidation
Section titled “Programmatic Page Cache Invalidation”In addition to automatic LRU eviction via keepAliveLimit, developers can manually purge cached page instances from memory using clearKeepAliveCache(pageName?: string).
This is useful when page instances hold stale data or user-specific state that must be cleared (e.g. after a user logs out or updates profile data).
Calling this.clearKeepAliveCache('UserProfilePage') (or app.clearKeepAliveCache('UserProfilePage')) evicts the specified cached page instance from memory and triggers its onUnmount() hook. Calling this.clearKeepAliveCache() without arguments purges all cached page instances.
// Inside a Component Action (e.g. Logout / Refresh Button)export default { actions: { handleLogout() { // Evict specific cached page instance this.clearKeepAliveCache('UserProfilePage');
// Or purge all cached keep-alive pages this.clearKeepAliveCache();
// Navigate to login this.$router.navigate('/login'); } }};3. Dynamic Route Parameters
Section titled “3. Dynamic Route Parameters”Route segments starting with : are dynamic variables. The values parsed from the URL are automatically added to the Page component’s state object and can be read inside templates or actions:
<!-- state.id will contain the value from /profile/:id --><div class="profile"> <h1>Viewing Profile ID: {{ id }}</h1></div>Query Parameters
Section titled “Query Parameters”The portion of a route hash after ? is automatically parsed into an object and made available as state.query. This works alongside dynamic parameters (:id) and can be read the same way,in templates or actions:
<!-- #/dashboard?tab=analytics&user=123 ->state.query.tab==='analytics' --> <div class="dashboard"> <h1>Current tab: {{ query.tab }}</h1> </div>Query parameters are also available inside component actions using this.state.query:
onMount() { const tab = this.state.query.tab; this.loadTabData(tab);}Type Coercion
Section titled “Type Coercion”While dynamic route parameters are always strings, query parameter values on the other hand are coerced based on their content:
| Raw value | Parsed as |
|---|---|
"true" |
Boolean true |
"false" |
Boolean false |
A numeric string(e.g. "123") |
Number (e.g.123) |
| Anything else | String |
//#/settings?darkMode=true&fontSize=16&theme=bluestate.query={ darkMode : true, //boolean fontSize:16, //number theme: 'blue' //string}Wildcard Path Matchers
Section titled “Wildcard Path Matchers”A * inside a route pattern acts as a catch-all wildcard, matching any subpath at that position — including nested segments separated by /. This is distinct from a route whose entire pattern is *, which is a router-wide fallback (see Configuring the Router); a pattern like /docs/* still only matches paths that start with /docs/:
app.initRouter({ '/docs/*': 'Docs',});The matched subpath is exposed as state.wildcard, just like a :param value:
<!-- /docs/intro -> state.wildcard === 'intro' --><!-- /docs/concepts/reactivity -> state.wildcard === 'concepts/reactivity' --><div class="docs"> <h1>Viewing: {{ wildcard }}</h1></div>Accessing Active Route Data
Section titled “Accessing Active Route Data”Components can access information about the currently active route using the reactive $route getter provided by AvenxComponent.
The $route object exposes the following properties:
| Property | Description |
|---|---|
$route.params |
Contains the dynamic route parameters extracted from the current URL. |
$route.hash |
Returns the current route hash. |
$route.page |
Returns the active page associated with the current route. |
Example
Section titled “Example”The following example shows how to access a route parameter inside a component:
import { AvenxComponent } from "avenx-core/runtime";
export default class UserProfile extends AvenxComponent { onMount() { console.log(this.$route.params.id); }}If the current route is:
Then:
this.$route.params.id; // "42"4. In-Place Parameter Updates
Section titled “4. In-Place Parameter Updates”If a page relies solely on onMount() to fetch data based on a route parameter, that data becomes stale after navigating to a matching route with a different parameter, since onMount() only runs once, when the page is first mounted.
To react correctly to parameter changes, compare the incoming value against the previously seen value inside onUpdate(), and only re-fetch when it has actually changed:
onMount() { this._lastId = this.state.id; this.fetchProfile(this.state.id);}onUpdate() { if (this.state.id !== this._lastId) { this._lastId = this.state.id; this.fetchProfile(this.state.id); }}See Page Reuse During Navigation in the AvenxPage API reference for more detail on when a page instance is reused versus recreated.
5. Multi-Router Setup & Namespaces
Section titled “5. Multi-Router Setup & Namespaces”Avenx-JS supports running multiple independent AvenxRouter instances at the same time on the same page — for example, a host application and one or more embedded micro-frontends, each with their own routes, pages, and navigation lifecycle.
Isolating routers with prefix
Section titled “Isolating routers with prefix”Each router created via app.initRouter(routes, options) can be given a prefix in its options. A router only ever handles hashes that start with its own prefix — any hash that doesn’t match is ignored completely by that router, including its wildcard route.
Route patterns are written relative to the prefix, not including it:
// Host app — no prefix, owns the root of the hash spaceconst hostApp = new AvenxApp({ target: '#app' });hostApp.registerPage('Home', Home);hostApp.initRouter({ '/': 'Home', '*': 'Home',});// Embedded widget — everything under #/widget/... belongs to this routerconst widgetApp = new AvenxApp({ target: '#widget' });widgetApp.registerPage('WidgetHome', WidgetHome);widgetApp.initRouter( { '/home': 'WidgetHome', // matches #/widget/home '*': 'WidgetHome', }, { prefix: '/widget' },);Navigating with router.navigate(hash) on a prefixed router automatically prepends its prefix, so calling navigate('#/home') on widgetApp’s router produces #/widget/home.
Coordinating wildcard fallbacks with window.__avenx_routers
Section titled “Coordinating wildcard fallbacks with window.__avenx_routers”Every AvenxRouter registers itself in a global window.__avenx_routers set when it’s created, and removes itself when destroy() is called. Routers use this registry to avoid stepping on each other’s wildcard (*) fallback routes.
When a router can’t match the current hash against any of its own named routes, it does not immediately fall back to its * route. Instead, it first checks every other router registered in window.__avenx_routers to see whether one of them owns that hash (respecting each router’s own prefix). Only if no other router claims the hash does the local wildcard fire.
This means, in the example above, if hostApp’s router doesn’t have a matching route for #/widget/home, it won’t incorrectly trigger its own * fallback — it detects that widgetApp’s router owns that hash and steps aside.
6. Page Titles
Section titled “6. Page Titles”When a route is resolved, the router can automatically update document.title. Add a title property to any route definition — either a static string or a dynamic function that receives the parsed route parameters:
app.initRouter({ '/': { page: 'Home', title: 'Home' }, '/profile/:id': { page: 'Profile', title: (params) => `Profile ${params.id}` }, '*': { page: 'NotFound', title: 'Page Not Found' },});Title Prefix & Suffix
Section titled “Title Prefix & Suffix”To avoid repeating your app name in every route, pass titlePrefix or titleSuffix in the router options. They are prepended / appended to every resolved title automatically:
app.initRouter( { '/': { page: 'Home', title: 'Home' }, '/about': { page: 'About', title: 'About Us' }, }, { titleSuffix: ' — MyApp' },);// Results in "Home — MyApp", "About Us — MyApp"7. Route Guards
Section titled “7. Route Guards”Guards decide whether a transition to a page is allowed. Create a guard using the CLI:
npx avenx g guard authImplement the canActivate(to, from) method. Return a boolean, a redirect string, or a Promise:
import { AvenxGuard } from 'avenx-core/runtime';export default class AuthGuard extends AvenxGuard { canActivate(to, from) { // Return true to allow, false to block, or hash path to redirect if (to.hash === '#/dashboard' && !window.isLoggedIn) { return '#/login'; } return true; }}Map guards to routes in your application router initialization:
app.initRouter({ '/': 'Home', '/dashboard': { page: 'Dashboard', guards: [AuthGuard] },});8. Nested Routes & Layout Components
Section titled “8. Nested Routes & Layout Components”AvenxRouter supports nested routing and persistent Layout components (children: [...], layout: Component), allowing child views to share persistent surrounding UI — such as navigation headers, sidebars, breadcrumbs, and footers — without re-rendering or unmounting the wrapper structure during navigation transitions.
Route Configuration Schema
Section titled “Route Configuration Schema”To configure nested routes, define a parent route object containing a layout component reference and a children array of child route definitions:
import AppLayout from './layouts/app-layout.component.js';
app.initRouter({ '/': { page: 'Home', title: 'Home' },
// Parent route with layout and nested children '/admin': { layout: AppLayout, children: [ { path: '/dashboard', page: 'AdminDashboard', title: 'Admin Dashboard' }, { path: '/users', page: 'AdminUsers', title: 'User Management' }, { path: '/settings', page: 'AdminSettings', title: 'System Settings' }, ], },
'*': { page: 'NotFound', title: 'Page Not Found' },});Layout Component Structure
Section titled “Layout Component Structure”A Layout component acts as a shell that wraps nested child page components. During child route navigation, the Layout component remains mounted, preserving its internal reactive state, animations, and DOM tree:
<state activeTab="'dashboard'" />
<action name="navigateTab"> const [tabPath] = args; this.state.activeTab = tabPath;</action>
<div class="admin-layout"> <!-- Persistent Sidebar Navigation --> <aside class="sidebar"> <nav> <a href="#/admin/dashboard" @click="navigateTab('dashboard')">Dashboard</a> <a href="#/admin/users" @click="navigateTab('users')">Users</a> <a href="#/admin/settings" @click="navigateTab('settings')">Settings</a> </nav> </aside>
<!-- Child View Mount Target --> <main class="content-view"> <slot></slot> </main></div>Key Behavior & Features
Section titled “Key Behavior & Features”1. Layout Persistence During Transitions
Section titled “1. Layout Persistence During Transitions”When navigating between child routes sharing the same parent layout (for example, moving from #/admin/dashboard to #/admin/users), AvenxRouter keeps the AppLayout instance intact. Only the child page component mounted inside the <slot> is swapped out. This eliminates layout flicker, preserves sidebar scroll positions, and prevents re-triggering API calls in AppLayout.onMount().
2. Parameter Inheritance
Section titled “2. Parameter Inheritance”Child routes automatically inherit dynamic path parameters defined on parent route paths.
app.initRouter({ '/org/:orgId': { layout: OrgLayout, children: [ { path: '/members/:memberId', page: 'MemberDetail' }, ], },});When navigating to #/org/acme-corp/members/42:
- Parent parameter
:orgId='acme-corp' - Child parameter
:memberId='42' - Both parameters are merged and passed into
MemberDetailpage component props and$route.params({ orgId: 'acme-corp', memberId: '42' }).
3. Multi-Level Layout Nesting
Section titled “3. Multi-Level Layout Nesting”AvenxRouter supports arbitrary levels of layout nesting. Each nested route layer renders its corresponding layout component, creating modular nested UI views:
app.initRouter({ '/app': { layout: GlobalAppLayout, children: [ { path: '/workspace/:id', layout: WorkspaceLayout, children: [ { path: '/kanban', page: 'KanbanPage' }, { path: '/timeline', page: 'TimelinePage' }, ], }, ], },});