Skip to content

Routing

Navigate between views with PACE.js's hash-based router.


Overview

PACE.js uses hash-based routing for single-page navigation:

/#product          → Product catalog
/#about            → About page
/#chat             → Chat widget
/#summary          → Executive summary
/#product/sql-mcp  → Product detail

Basic Navigation

Programmatic Navigation

javascript
import { PACE } from '@semanticintent/pace-pattern'

const pace = new PACE({ ... })
pace.mount()

// Navigate to views
pace.navigate('product')
pace.navigate('about')
pace.navigate('chat')
pace.navigate('summary')

// Navigate with parameters
pace.navigate('product', { id: 'sql-mcp' })
html
<a href="#product">Products</a>
<a href="#about">About</a>
<a href="#chat">Chat</a>
<a href="#summary">Summary</a>
<a href="#product/sql-mcp">SQL MCP</a>

Router API

Initialize

javascript
import { Router, State } from '@semanticintent/pace-pattern'

const state = new State()
const router = new Router(state)

router.init()
javascript
router.navigate('product')
router.navigate('product', { id: 'sql-mcp' })

Listen to Route Changes

javascript
router.on('navigate', (route, params) => {
  console.log('Navigated to:', route, params)
})

// Listen to all routes
router.on('*', (route, params) => {
  console.log('Route change:', route, params)
})

// Listen to specific route
router.on('product', (params) => {
  console.log('Product view:', params)
})

Route Parameters

URL Parameters

/#product/sql-mcp
javascript
router.on('product', ({ id }) => {
  console.log('Product ID:', id)  // 'sql-mcp'
  loadProduct(id)
})

Query Parameters

/#product?category=databases&sort=popular
javascript
router.on('product', ({ category, sort }) => {
  console.log('Category:', category)  // 'databases'
  console.log('Sort:', sort)          // 'popular'
  filterProducts({ category, sort })
})

Deep Linking

Share specific views:

javascript
// Share product link
const productUrl = `${window.location.origin}/#product/${product.id}`
navigator.clipboard.writeText(productUrl)

// Share chat with context
const chatUrl = `${window.location.origin}/#chat?topic=databases`

// Share summary
const summaryUrl = `${window.location.origin}/#summary`

Route Guards

Authentication Guard

javascript
router.beforeNavigate((to, from) => {
  if (to === 'admin' && !isAuthenticated()) {
    router.navigate('login')
    return false  // Cancel navigation
  }
  return true  // Allow navigation
})

Confirmation Guard

javascript
router.beforeNavigate((to, from) => {
  if (from === 'chat' && hasUnsavedMessages()) {
    return confirm('You have unsaved messages. Leave anyway?')
  }
  return true
})

Custom Routes

Add Custom Routes

javascript
// Add admin route
router.addRoute('admin', () => {
  state.set('activeView', 'admin')
  renderAdminPanel()
})

// Add settings route
router.addRoute('settings', ({ tab }) => {
  state.set('activeView', 'settings')
  state.set('settingsTab', tab || 'general')
  renderSettings()
})

// Usage
router.navigate('admin')
router.navigate('settings', { tab: 'theme' })

Route Transitions

Animated Transitions

javascript
router.on('navigate', async (to, from) => {
  // Fade out current view
  await fadeOut(getCurrentView())

  // Update state
  state.set('activeView', to)

  // Fade in new view
  await fadeIn(getNewView())
})

Loading States

javascript
router.on('navigate', async (to) => {
  // Show loading
  state.set('loading', true)

  // Load data if needed
  if (to === 'product') {
    await loadProducts()
  }

  // Hide loading
  state.set('loading', false)
})

Browser History

Back/Forward

PACE.js automatically handles browser back/forward:

javascript
// User clicks browser back button
// Router automatically navigates to previous route

Programmatic History

javascript
// Go back
window.history.back()

// Go forward
window.history.forward()

// Go to specific point
window.history.go(-2)

Scroll Behavior

Scroll to Top

javascript
router.on('navigate', () => {
  window.scrollTo({ top: 0, behavior: 'smooth' })
})

Preserve Scroll Position

javascript
const scrollPositions = new Map()

router.on('navigate', (to, from) => {
  // Save current scroll position
  if (from) {
    scrollPositions.set(from, window.scrollY)
  }

  // Restore scroll position
  const savedPosition = scrollPositions.get(to)
  if (savedPosition) {
    window.scrollTo({ top: savedPosition })
  } else {
    window.scrollTo({ top: 0 })
  }
})

Analytics Integration

Track Page Views

javascript
router.on('navigate', (route, params) => {
  gtag('config', 'GA_MEASUREMENT_ID', {
    page_path: `/#${route}`,
    page_title: getPageTitle(route)
  })
})

Track Route Timing

javascript
let routeStartTime

router.on('navigate', (route) => {
  if (routeStartTime) {
    const duration = Date.now() - routeStartTime
    gtag('event', 'timing_complete', {
      name: 'route_transition',
      value: duration,
      event_category: 'navigation'
    })
  }
  routeStartTime = Date.now()
})

Server-Side Rendering (SSR)

PACE.js uses hash-based routing, which works without SSR. But if you need SSR:

Convert to History API

javascript
class HistoryRouter extends Router {
  init() {
    window.addEventListener('popstate', () => {
      this.handleRoute(window.location.pathname)
    })
  }

  navigate(route, params) {
    const url = this.buildUrl(route, params)
    window.history.pushState({}, '', url)
    this.handleRoute(route, params)
  }
}

Server Configuration

javascript
// Express.js
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'index.html'))
})

// Nginx
location / {
  try_files $uri $uri/ /index.html;
}

Nested Routes

javascript
// Define nested routes
router.addRoute('admin/users', () => {
  renderAdminUsers()
})

router.addRoute('admin/settings', () => {
  renderAdminSettings()
})

// Navigate
router.navigate('admin/users')
router.navigate('admin/settings')

Route Metadata

javascript
const routes = new Map([
  ['product', { title: 'Products', requiresAuth: false }],
  ['admin', { title: 'Admin Panel', requiresAuth: true }],
  ['settings', { title: 'Settings', requiresAuth: true }]
])

router.on('navigate', (route) => {
  const meta = routes.get(route)

  // Update page title
  document.title = `${meta.title} | PACE App`

  // Check auth
  if (meta.requiresAuth && !isAuthenticated()) {
    router.navigate('login')
  }
})

Best Practices

1. Use Hash Routes for Static Hosting

✅ Hash routes work on GitHub Pages, Netlify, Vercel without config

2. Always Handle 404

javascript
router.on('404', () => {
  state.set('activeView', 'not-found')
})

3. Validate Route Parameters

javascript
router.on('product', ({ id }) => {
  if (!id || !products.find(p => p.id === id)) {
    router.navigate('product')
    return
  }
  loadProduct(id)
})

4. Use Route Constants

javascript
const ROUTES = {
  PRODUCT: 'product',
  ABOUT: 'about',
  CHAT: 'chat',
  SUMMARY: 'summary'
}

router.navigate(ROUTES.PRODUCT)

Navigate seamlessly with PACE routing! 🧭