Router API Reference

On this page 28

This document details stx's routing system API, including route configuration, navigation guards, and route management.

Page metadata on navigation

STX fragment responses include an inert <template data-stx-page-head="1"> payload. On navigation, the router synchronizes the destination title, description, Open Graph (og:*) and Twitter (twitter:*) metadata, canonical link, and locale alternate links (rel="alternate" with hreflang). Prefetch does not change the active head; cached and retained back/forward visits restore the destination metadata. Supported tags missing from the new payload are removed.

This is not a whole-head replacement. Scripts, stylesheets, preload links, security tokens, theme metadata, and unrelated head state are outside this contract. Only the supported tags' semantic attributes are copied. Older clients leave the template inert; a new client receiving an older server's fragment without a payload leaves its existing metadata unchanged. Full-document SPA swaps use the same metadata allowlist. Full browser navigations are unchanged. Authored numeric and named HTML entities are preserved for a single browser decode, including literal entity text such as &amp;lt;. The parsed payload title takes precedence over the legacy X-STX-Title header; that header remains the fallback for older fragment responses.

Router Setup

Basic Configuration

import { createRouter } from '@stacksjs/stx/router'

const router = createRouter({
  // Base URL for all routes
  base: '/',

  // Route definitions
  routes: [
    {
      path: '/',
      component: Home
    },
    {
      path: '/about',
      component: About
    },
    {
      path: '/users/:id',
      component: UserProfile,
      props: true
    }
  ],

  // Scroll behavior
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    }
    return { top: 0 }
  }
})

Route Configuration

interface RouteConfig {
  // Route path
  path: string

  // Route name (optional)
  name?: string

  // Component to render
  component: Component

  // Pass route params as props
  props?: boolean | Object | Function

  // Nested routes
  children?: RouteConfig[]

  // Route metadata
  meta?: Record<string, any>

  // Route alias
  alias?: string | string[]

  // Redirect configuration
  redirect?: string | Location | Function

  // Route-specific navigation guards
  beforeEnter?: NavigationGuard

  // Lazy loading
  component: () => import('./UserProfile.stx')
}

Route Definition

Basic Routes

const routes = [
  // Static routes
  {
    path: '/',
    component: Home
  },

  // Dynamic segments
  {
    path: '/users/:id',
    component: UserProfile
  },

  // Optional parameters
  {
    path: '/posts/:id?',
    component: PostView
  },

  // Multiple dynamic segments
  {
    path: '/orgs/:orgId/repos/:repoId',
    component: RepoView
  },

  // Catch all / 404
  {
    path: '/:pathMatch(.*)*',
    component: NotFound
  }
]

Nested Routes

const routes = [
  {
    path: '/user',
    component: User,
    children: [
      {
        // /user/profile
        path: 'profile',
        component: UserProfile
      },
      {
        // /user/posts
        path: 'posts',
        component: UserPosts
      },
      {
        // /user/settings
        path: 'settings',
        component: UserSettings
      }
    ]
  }
]

Named Routes

const routes = [
  {
    path: '/user/:id',
    name: 'user',
    component: User
  }
]

// Navigate using route name
router.push({ name: 'user', params: { id: '123' } })

Global Guards

// Before each route change
router.beforeEach((to, from) => {
  // Check authentication
  if (to.meta.requiresAuth && !isAuthenticated) {
    return '/login'
  }
})

// After each route change
router.afterEach((to, from) => {
  // Analytics tracking
  trackPageView(to.fullPath)
})

// Route error handling
router.onError((error) => {
  // Handle navigation errors
  console.error('Navigation error:', error)
})

Route-Specific Guards

const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from) => {
      // Check admin access
      if (!hasAdminAccess) {
        return '/unauthorized'
      }
    }
  }
]

Component Guards

const AdminDashboard = defineComponent({
  setup() {
    // Called before component is created
    onBeforeRouteEnter((to, from) => {
      // No access to 'this'
    })

    // Called before route changes
    onBeforeRouteUpdate((to, from) => {
      // Can access component instance
    })

    // Called before leaving route
    onBeforeRouteLeave((to, from) => {
      // Prevent accidental navigation
      const answer = window.confirm('Leave page?')
      if (!answer) return false
    })
  }
})

Programmatic Navigation

// Push new route
router.push('/about')

// Push with object
router.push({
  path: '/users',
  query: { page: 2 }
})

// Push with name
router.push({
  name: 'user',
  params: { id: '123' }
})

// Replace current route
router.replace('/other')

// Go back/forward
router.go(-1)  // back
router.go(1)   // forward

// Push with hash
router.push({
  path: '/about',
  hash: '#team'
})

Route Information

// Current route
const route = router.currentRoute.value

// Access route data
console.log(route.path)       // Current path
console.log(route.params)     // Route parameters
console.log(route.query)      // Query parameters
console.log(route.hash)       // URL hash
console.log(route.fullPath)   // Full URL
console.log(route.name)       // Route name
console.log(route.meta)       // Route metadata

// Check current route
router.isReady()             // Router initialized
router.hasRoute('name')      // Route exists

Route Composition

Route Utilities

import { useRoute, useRouter } from '@stacksjs/stx/router'

// In component setup
const route = useRoute()
const router = useRouter()

// Access route info
console.log(route.params.id)
console.log(route.query.page)

// Navigate
router.push('/new-path')

Route Guards Composition

import { onBeforeRouteLeave, onBeforeRouteUpdate } from '@stacksjs/stx/router'

// In component setup
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('Leave page?')
  if (!answer) return false
})

onBeforeRouteUpdate((to, from) => {
  // Handle route updates
})

Advanced Features

Route Meta Fields

const routes = [
  {
    path: '/admin',
    component: Admin,
    meta: {
      requiresAuth: true,
      roles: ['admin'],
      title: 'Admin Dashboard'
    }
  }
]

router.beforeEach((to, from) => {
  // Check meta requirements
  if (to.meta.requiresAuth) {
    // Auth check
  }

  if (to.meta.roles) {
    // Role check
  }

  // Update page title
  document.title = to.meta.title
})

Dynamic Routes

// Lazy loading
const routes = [
  {
    path: '/user/:id',
    component: () => import('./views/User.stx')
  }
]

// Dynamic route matching
const routes = [
  // Match /user-1, /user-2, etc.
  {
    path: '/user-:id(\\d+)',
    component: UserById
  },

  // Optional parameters
  {
    path: '/optional/:id?',
    component: OptionalId
  },

  // Custom regex
  {
    path: '/:id(\\d+)',
    component: NumberId
  }
]
// Handle navigation failures
router.push('/path').catch(failure => {
  if (failure.type === NavigationFailureType.aborted) {
    // Navigation aborted
  }

  if (failure.type === NavigationFailureType.cancelled) {
    // Navigation cancelled
  }
})

// Check navigation status
const navigation = await router.push('/path')
if (navigation.aborted) {
  // Handle aborted navigation
}

STX provides auto-imported navigation functions that replace raw window.location and window.history usage. These work in <script client> blocks without any imports.

Navigate to a URL. Uses the SPA router (window.stxRouter) when available, otherwise falls back to window.location.href.

// In <script client> — auto-imported
navigate('/about')
navigate('/users/123')
navigate('https://external.com')
<template>
  <button @click="navigate('/dashboard')">Dashboard</button>
</template>

goBack() / goForward()

<template>
  <button @click="goBack()">Back</button>
  <button @click="goForward()">Forward</button>
</template>

useRoute()

Reactive route information. Returns getters that always reflect the current URL.

const route = useRoute()

route.path      // '/about'
route.fullPath  // '/about?page=2#section'
route.hash      // '#section'
route.query     // { page: '2' }
route.params    // {} (from stxRouter if available)

useSearchParams()

Reactive URL search parameters. Automatically syncs with popstate and stx:navigate events.

const search = useSearchParams()

search.get('page')              // '2'
search.has('page')              // true
search.set('page', '3')         // Updates the URL, pushes a history entry
search.setAll({ page: '1', sort: 'name' })  // Set multiple params
search.delete('page')           // Remove one

Every mutator takes an optional { replace: true }, which replaces the current history entry instead of pushing a new one:

// Consuming a one-shot param — an OAuth callback, ?checkout=success, a flash
// token. Without `replace` the URL still carrying `code` stays in history, so
// Back replays the callback.
search.delete('code', { replace: true })

Next Steps