@flightdev/router
v0.5.0
Published
Agnostic client-side routing primitives for Flight Framework
Maintainers
Readme
@flightdev/router
Universal client-side routing primitives for Flight Framework. Zero external dependencies. Works with any UI framework.
Table of Contents
- Features
- Installation
- Quick Start
- Prefetch Strategies
- Smart Prefetching
- Intercepting Routes
- Programmatic Prefetching
- Prefetch Queue
- React Hooks
- Programmatic Navigation
- Route Matching
- Link Component
- Framework Integration
- SSR Support
- Browser Support
- License
Features
- Multiple prefetch strategies (none, intent, render, viewport, idle)
- Network-aware smart prefetching (respects Save-Data and connection speed)
- Intercepting routes with modal support
- Prefetch queue with concurrency control
- SSR-safe implementation
- Framework-agnostic with React adapters
- TypeScript-first design
- IntersectionObserver for viewport prefetching
- Zero external dependencies
Installation
npm install @flightdev/routerQuick Start
With React
import { RouterProvider, Link, useRouter } from '@flightdev/router';
function App() {
return (
<RouterProvider initialPath={url}>
<Navigation />
<Content />
</RouterProvider>
);
}
function Navigation() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/docs" prefetch="intent">Docs</Link>
<Link href="/about">About</Link>
</nav>
);
}
function Content() {
const { path, navigate } = useRouter();
return (
<main>
<p>Current path: {path}</p>
<button onClick={() => navigate('/dashboard')}>
Go to Dashboard
</button>
</main>
);
}Prefetch Strategies
Flight Router supports multiple prefetch strategies to optimize navigation performance:
| Strategy | Behavior | Best For |
|----------|----------|----------|
| 'none' | No prefetching (default) | Low-priority links |
| 'intent' | Prefetch on hover/focus | Most navigation links |
| 'render' | Prefetch immediately | Critical paths (checkout) |
| 'viewport' | Prefetch when visible | Mobile, infinite scroll |
| 'idle' | Prefetch when browser is idle | Background preloading |
Usage Examples
// No prefetching (default)
<Link href="/docs">Docs</Link>
<Link href="/docs" prefetch="none">Docs</Link>
// Prefetch on hover/focus (recommended for most cases)
<Link href="/docs" prefetch="intent">Docs</Link>
<Link href="/docs" prefetch>Docs</Link> // boolean true = "intent"
// Prefetch immediately when link renders
<Link href="/checkout" prefetch="render">Checkout</Link>
// Prefetch when link enters viewport (good for mobile)
<Link href="/products" prefetch="viewport">Products</Link>
// Prefetch when browser is idle
<Link href="/settings" prefetch="idle">Settings</Link>Smart Prefetching
Network-aware prefetching that respects user preferences and connection quality.
Features
- Respects
Save-Dataheader (users on limited data plans) - Detects connection type (2G, 3G, 4G)
- Configurable minimum network requirements
- Automatic throttling on slow connections
API
import {
smartPrefetch,
shouldSkipPrefetch,
canPrefetchOnNetwork,
} from '@flightdev/router';
// Respects network conditions automatically
smartPrefetch('/dashboard');
// Check if prefetching should be skipped
if (shouldSkipPrefetch()) {
console.log('Skipping due to network conditions');
}
// Custom network requirements
const canPrefetch = canPrefetchOnNetwork({
respectSaveData: true, // Skip if Save-Data enabled
respectSlowNetwork: true, // Skip on 2G/slow-2G
minEffectiveType: '3g', // Minimum 3G required
});Configuration
smartPrefetch('/page', {
respectSaveData: true, // Default: true
respectSlowNetwork: true, // Default: true
minEffectiveType: '3g', // Default: '3g'
priority: 'high', // Optional priority override
});Intercepting Routes
Render routes in modals while preserving URL state. Perfect for photo galleries, user profiles, and detail views.
React Integration
import {
InterceptedRouteProvider,
useInterceptedRoute,
} from '@flightdev/router';
// Wrap your layout
function RootLayout({ children }) {
return (
<InterceptedRouteProvider>
{children}
<ModalRenderer />
</InterceptedRouteProvider>
);
}
// Render intercepted content in a modal
function ModalRenderer() {
const intercepted = useInterceptedRoute();
if (!intercepted) return null;
return (
<Dialog open onClose={intercepted.dismiss}>
<intercepted.Component />
<button onClick={intercepted.navigateToPage}>
View Full Page
</button>
</Dialog>
);
}Intercepted Route State
| Property | Type | Description |
|----------|------|-------------|
| Component | () => unknown | The component to render |
| pathname | string | The intercepted URL path |
| params | Record<string, string> | Route parameters |
| dismiss() | () => void | Go back (close modal) |
| navigateToPage() | () => void | Navigate to actual page |
Additional Hooks
import {
useInterceptedRoute,
useSetInterceptedRoute,
useIsIntercepting,
} from '@flightdev/router';
// Check if currently intercepting
const isIntercepting = useIsIntercepting();
// Programmatically set intercepted state (for router integration)
const setIntercepted = useSetInterceptedRoute();Programmatic Prefetching
import {
prefetch,
prefetchAll,
prefetchWhenIdle,
isPrefetched,
clearPrefetchCache,
} from '@flightdev/router';
// Basic prefetch
prefetch('/docs');
// High priority prefetch
prefetch('/checkout', { priority: 'high' });
// Prefetch with data loaders
prefetch('/products', { includeData: true });
// Prefetch multiple pages
prefetchAll(['/page1', '/page2', '/page3']);
// Prefetch when browser is idle
prefetchWhenIdle('/dashboard');
// Check if already prefetched
if (!isPrefetched('/docs')) {
prefetch('/docs');
}
// Clear prefetch cache (useful for testing)
clearPrefetchCache();Prefetch Queue
Control concurrent prefetch requests to avoid overwhelming the network:
import { PrefetchQueue } from '@flightdev/router';
// Create queue with max 3 concurrent prefetches
const queue = new PrefetchQueue(3);
// Add URLs to queue
queue.add('/page1');
queue.add('/page2', { priority: 'high' });
queue.addAll(['/page3', '/page4', '/page5']);
// Control queue
queue.pause(); // Stop processing
queue.resume(); // Continue processing
queue.clear(); // Remove pending items
// Check state
console.log(queue.pending); // Items waiting
console.log(queue.activeCount); // Currently prefetchingReact Hooks
import {
useRouter,
useParams,
useSearchParams,
usePathname,
} from '@flightdev/router';
// Get current path and navigation functions
const { path, searchParams, navigate, back, forward } = useRouter();
// Get route parameters (from dynamic routes like [slug])
const { slug } = useParams<{ slug: string }>();
// Get and set search params
const [searchParams, setSearchParams] = useSearchParams();
// Get current pathname
const pathname = usePathname();Programmatic Navigation
import { navigate, prefetch } from '@flightdev/router';
// Navigate to a path
navigate('/docs');
// Replace history instead of push
navigate('/login', { replace: true });
// Navigate without scrolling to top
navigate('/next-page', { scroll: false });
// Pass state data
navigate('/dashboard', { state: { from: '/login' } });Route Matching
import { matchRoute, parseParams, generatePath } from '@flightdev/router';
// Check if a path matches a pattern
const { matched, params } = matchRoute('/docs/routing', '/docs/:slug');
// matched: true, params: { slug: 'routing' }
// Parse params from a path
const params = parseParams('/blog/2024/my-post', '/blog/:year/:slug');
// { year: '2024', slug: 'my-post' }
// Generate a path from pattern and params
const path = generatePath('/docs/:slug', { slug: 'api-routes' });
// '/docs/api-routes'Link Component
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| href | string | required | Target URL path |
| prefetch | boolean \| PrefetchStrategy | 'none' | Prefetch strategy |
| replace | boolean | false | Replace history entry |
| scroll | boolean | true | Scroll to top on navigate |
| target | string | - | Link target (_blank, etc) |
| className | string | - | CSS class |
| aria-label | string | - | Accessibility label |
PrefetchPageLinks Component
For proactive prefetching based on user behavior:
import { PrefetchPageLinks } from '@flightdev/router';
function SearchResults({ results }) {
return (
<>
{results.map(result => (
<div key={result.id}>
<PrefetchPageLinks page={result.url} />
<Link href={result.url}>{result.title}</Link>
</div>
))}
</>
);
}Framework Integration
Vue
<script setup>
import { useLinkProps } from '@flightdev/router';
const docsLink = useLinkProps('/docs', { prefetch: 'intent' });
</script>
<template>
<a v-bind="docsLink">Documentation</a>
</template>Vanilla JavaScript
import { createLink, prefetch } from '@flightdev/router';
const link = createLink({
href: '/docs',
children: 'Documentation',
prefetch: 'intent',
});
document.body.appendChild(link);SSR Support
The router is SSR-safe. Pass the initial path from your server:
// entry-server.tsx
export function render(url: string) {
return renderToString(
<RouterProvider initialPath={url}>
<App />
</RouterProvider>
);
}Browser Support
| Feature | Chrome | Firefox | Safari | Edge | |---------|--------|---------|--------|------| | Prefetch | All | All | All | All | | Viewport (IntersectionObserver) | 51+ | 55+ | 12.1+ | 15+ | | Network Information API | 61+ | No | No | 79+ | | requestIdleCallback | 47+ | 55+ | No | 79+ | | fetchPriority | 101+ | No | No | 101+ |
Features gracefully degrade when APIs are unavailable.
TypeScript
Full TypeScript support with exported types:
import type {
PrefetchStrategy,
PrefetchOptions,
SmartPrefetchOptions,
InterceptedRouteState,
InterceptedRouteContextValue,
} from '@flightdev/router';License
MIT
