@react-buoy/route-events
v1.5.26
Published
route-events package
Readme
@react-buoy/route-events
A comprehensive route tracking and visualization toolkit for Expo Router applications. Monitor route changes, inspect navigation state, explore your route sitemap, and visualize the navigation stack.
Features
- Route Event Tracking: Automatically track all route changes with detailed event information
- Route Sitemap: Browse all available routes in your app with search and navigation
- Navigation Stack Viewer: Inspect the current navigation stack in real-time
- Event Timeline: View a chronological timeline of all route changes
- Filtering & Search: Filter events by pathname patterns and search through routes
- Persistent State: Remembers your monitoring preferences and filters
- Full Modal UI: Beautiful, production-ready modal interface for all features
Installation
This package is part of the React Buoy monorepo and is automatically available to other packages and the example app.
For external projects:
npm install @react-buoy/route-events
# or
pnpm add @react-buoy/route-events
# or
yarn add @react-buoy/route-eventsQuick Start
Simplest Setup - Just 1 Line!
Import the preset and add it to your tools array. Done!
import { routeEventsToolPreset } from '@react-buoy/route-events';
import { FloatingDevTools } from '@react-buoy/core';
const installedApps = [
routeEventsToolPreset, // That's it! One line.
// ...your other tools
];
function App() {
return (
<FloatingDevTools
apps={installedApps}
environment="local"
userRole="admin"
/>
);
}Done! The preset automatically:
- ✅ Starts tracking routes when opened
- ✅ Uses the built-in route observer singleton
- ✅ Provides all three tabs (Routes, Events, Stack)
- ✅ No configuration, no props, no hooks to call
Requirements
@react-buoy/route-events relies on the same core libraries as Expo Router itself:
expo-router≥ 2.0.0 (the Routes tab readsstore.routeNodedirectly)@react-navigation/native≥ 7.x (Stack tab + Expo Router routing hooks)@react-native-async-storage/async-storage(state persistence)react/react-native
These packages are declared as peerDependencies, so your app decides the exact versions. When a dependency is missing or not initialized, the devtool logs a clear [RouteEvents] ... console message (in dev builds) and gracefully disables the affected feature instead of crashing. See packages/route-events/docs/ROUTES_SITEMAP_ACCESS_GUIDE.md for the full data-flow and troubleshooting guide.
Alternative: Manual Setup
If you're not using FloatingDevTools or want more control:
import { RouteEventsModalWithTabs } from '@react-buoy/route-events';
function App() {
const [showRoutes, setShowRoutes] = useState(false);
return (
<>
<Button onPress={() => setShowRoutes(true)}>
Open Route Inspector
</Button>
<RouteEventsModalWithTabs
visible={showRoutes}
onClose={() => setShowRoutes(false)}
/>
</>
);
}API Reference
Core Components
RouteEventsModalWithTabs
The main modal interface with three tabs: Routes, Events, and Stack.
Props:
interface RouteEventsModalWithTabsProps {
/** Whether the modal is visible */
visible: boolean;
/** Callback when modal is closed */
onClose: () => void;
/** Optional back button handler */
onBack?: () => void;
/** Whether to use shared modal dimensions */
enableSharedModalDimensions?: boolean;
/**
* Optional route observer instance.
* If not provided, uses the default singleton (recommended).
* Route tracking starts automatically when the modal is opened.
*/
routeObserver?: RouteObserver;
}Example:
<RouteEventsModalWithTabs
visible={isVisible}
onClose={handleClose}
/>Advanced Example (with custom observer):
Only needed if you want to use a custom route observer instance:
import { RouteEventsModalWithTabs, routeObserver } from '@react-buoy/route-events';
<RouteEventsModalWithTabs
visible={isVisible}
onClose={handleClose}
routeObserver={routeObserver} // Optional - uses default if not provided
/>RoutesSitemap
Standalone component for browsing all routes in your application.
Props:
interface RoutesSitemapProps {
style?: StyleProp<ViewStyle>;
}Example:
import { RoutesSitemap } from '@react-buoy/route-events';
function RoutesScreen() {
return <RoutesSitemap />;
}NavigationStack
Standalone component for visualizing the current navigation stack.
Props:
interface NavigationStackProps {
style?: StyleProp<ViewStyle>;
}Example:
import { NavigationStack } from '@react-buoy/route-events';
function DebugScreen() {
return <NavigationStack />;
}Hooks
useRouteObserver
Hook to track route changes and emit events to the global observer.
Note: You typically don't need to use this hook directly - the RouteEventsModalWithTabs component starts tracking automatically. This hook is only needed for advanced use cases like custom analytics.
Signature:
function useRouteObserver(
callback?: (event: RouteChangeEvent) => void
): voidExamples:
// Only needed for custom analytics - modal handles tracking automatically!
import { useRouteObserver } from '@react-buoy/route-events';
export default function RootLayout() {
useRouteObserver((event) => {
analytics.trackPageView(event.pathname);
});
return <Stack />;
}useRouteSitemap
Access route information from your app's file-based routing structure.
Signature:
function useRouteSitemap(
options?: UseRouteSitemapOptions
): UseRouteSitemapResultOptions:
interface UseRouteSitemapOptions {
/** Whether to include layout routes */
includeLayouts?: boolean;
/** Whether to include route groups */
includeGroups?: boolean;
}Result:
interface UseRouteSitemapResult {
/** All routes in the application */
routes: RouteInfo[];
/** Routes grouped by directory */
groups: RouteGroup[];
/** Statistics about the routes */
stats: RouteStats;
/** Get a specific route by path */
getRoute: (path: string) => RouteInfo | undefined;
/** Get all parent routes of a path */
getParents: (path: string) => RouteInfo[];
}Example:
import { useRouteSitemap } from '@react-buoy/route-events';
function RouteDebugger() {
const { routes, stats, getRoute } = useRouteSitemap();
console.log(`Total routes: ${stats.total}`);
console.log(`Dynamic routes: ${stats.byType.dynamic}`);
const homeRoute = getRoute('/');
// { path: '/', type: 'index', ... }
return (
<View>
{routes.map(route => (
<Text key={route.path}>{route.path}</Text>
))}
</View>
);
}useNavigationStack
Access and control the current navigation stack.
Signature:
function useNavigationStack(): UseNavigationStackResultResult:
interface UseNavigationStackResult {
/** The current navigation stack */
stack: StackDisplayItem[];
/** Whether the data is loading */
loading: boolean;
/** Navigate to a specific stack index */
navigateToIndex: (index: number) => void;
/** Pop back to a specific index */
popToIndex: (index: number) => void;
/** Go back one screen */
goBack: () => void;
/** Go to the root of the stack */
popToTop: () => void;
}Example:
import { useNavigationStack } from '@react-buoy/route-events';
function NavigationDebugger() {
const { stack, goBack, popToTop } = useNavigationStack();
return (
<View>
<Text>Stack Depth: {stack.length}</Text>
<Button onPress={goBack}>Go Back</Button>
<Button onPress={popToTop}>Go to Root</Button>
{stack.map((item, index) => (
<Text key={item.key}>
{index}: {item.displayPath}
</Text>
))}
</View>
);
}Utilities
RouteObserver
Singleton instance for observing route changes.
Methods:
class RouteObserver {
/** Emit a route change event */
emit(event: RouteChangeEvent): void;
/** Add a listener for route changes */
addListener(callback: (event: RouteChangeEvent) => void): () => void;
/** Remove a listener */
removeListener(callback: (event: RouteChangeEvent) => void): void;
}Example:
import { routeObserver } from '@react-buoy/route-events';
// Add listener
const unsubscribe = routeObserver.addListener((event) => {
console.log('Route changed:', event.pathname);
});
// Later, remove listener
unsubscribe();RouteParser
Utility for parsing Expo Router's route structure.
Methods:
class RouteParser {
/** Get all routes from the router */
static getRoutes(options?: {
includeLayouts?: boolean;
includeGroups?: boolean;
}): RouteInfo[];
/** Group routes by directory */
static groupRoutes(routes: RouteInfo[]): RouteGroup[];
/** Get statistics about routes */
static getStats(routes: RouteInfo[]): RouteStats;
}Types
RouteChangeEvent
interface RouteChangeEvent {
/** Current pathname */
pathname: string;
/** Route parameters */
params: Record<string, string | string[]>;
/** Route segments */
segments: string[];
/** Timestamp of the change */
timestamp: number;
/** Previous pathname (if available) */
previousPathname?: string;
/** Time since previous navigation in ms */
timeSincePrevious?: number;
}RouteInfo
interface RouteInfo {
/** Full path of the route */
path: string;
/** Route type */
type: RouteType;
/** Route name/file */
name: string;
/** Dynamic parameters */
params: string[];
/** Whether it's a layout */
isLayout: boolean;
/** Whether it's a route group */
isGroup: boolean;
/** Parent path */
parent?: string;
}RouteType
type RouteType =
| 'index' // index routes (/)
| 'static' // static routes (/about)
| 'dynamic' // dynamic routes (/user/[id])
| 'catch-all' // catch-all routes (/docs/[...path])
| 'layout' // layout routes (_layout.tsx)
| 'group'; // route groups ((tabs))Use Cases
Analytics Integration
import { useRouteObserver } from '@react-buoy/route-events';
export default function RootLayout() {
useRouteObserver((event) => {
// Track page views
analytics.trackPageView({
path: event.pathname,
params: event.params,
previousPath: event.previousPathname,
timeSpent: event.timeSincePrevious,
});
});
return <Stack />;
}Performance Monitoring
import { useRouteObserver } from '@react-buoy/route-events';
export default function RootLayout() {
useRouteObserver((event) => {
if (event.timeSincePrevious && event.timeSincePrevious > 1000) {
// Log slow navigations
console.warn('Slow navigation:', {
from: event.previousPathname,
to: event.pathname,
duration: event.timeSincePrevious,
});
}
});
return <Stack />;
}Development Tools
import { RouteEventsModalWithTabs, routeObserver } from '@react-buoy/route-events';
function DevTools() {
const [visible, setVisible] = useState(false);
// Only show in development
if (__DEV__) {
return (
<>
<TouchableOpacity
style={styles.devButton}
onPress={() => setVisible(true)}
>
<Text>🧭 Routes</Text>
</TouchableOpacity>
<RouteEventsModalWithTabs
visible={visible}
onClose={() => setVisible(false)}
routeObserver={routeObserver}
/>
</>
);
}
return null;
}Route Documentation
import { useRouteSitemap } from '@react-buoy/route-events';
function RouteDocumentation() {
const { routes, groups, stats } = useRouteSitemap({
includeLayouts: false,
includeGroups: false,
});
return (
<ScrollView>
<Text>Total Routes: {stats.total}</Text>
{groups.map(group => (
<View key={group.name}>
<Text>{group.name} ({group.routes.length})</Text>
{group.routes.map(route => (
<Text key={route.path}>
{route.path} - {route.type}
</Text>
))}
</View>
))}
</ScrollView>
);
}Features in Detail
Routes Tab
- Browse all routes in your application
- Search routes by path or name
- View route types (static, dynamic, catch-all)
- Navigate to any route directly
- Handles dynamic route parameters with prompts
- Group and layout route identification
Events Tab
- Real-time route change monitoring
- Toggle monitoring on/off
- Event timeline with timestamps
- Filter events by pathname patterns
- Search through event history
- View detailed event information
- Compare route changes (diff view)
- Clear event history
- Persistent filter preferences
Stack Tab
- Real-time navigation stack visualization
- Stack depth indicator
- Navigate to any stack level
- Pop to specific stack index
- Quick navigation controls
- Route parameter display
Dependencies
@react-buoy/shared-ui- Common UI components and utilitiesexpo-router- Expo Router integration (peer dependency)@react-navigation/native- React Navigation integration (peer dependency)@react-native-async-storage/async-storage- For persistent preferences (peer dependency)- React and React Native (peer dependencies)
Development
Building
pnpm buildType Checking
pnpm typecheckClean Build
pnpm cleanPackage Structure
route-events/
├── src/
│ ├── components/ # UI components
│ │ ├── RouteEventsModalWithTabs.tsx
│ │ ├── RoutesSitemap.tsx
│ │ ├── NavigationStack.tsx
│ │ ├── RouteEventsTimeline.tsx
│ │ ├── RouteEventItemCompact.tsx
│ │ ├── RouteEventDetailContent.tsx
│ │ ├── RouteEventExpandedContent.tsx
│ │ └── RouteFilterViewV2.tsx
│ ├── RouteObserver.ts # Event system
│ ├── RouteParser.ts # Route parsing utilities
│ ├── useRouteObserver.ts # Route tracking hook
│ ├── useRouteSitemap.ts # Sitemap access hook
│ ├── useNavigationStack.ts # Stack access hook
│ └── index.tsx # Main exports
├── lib/ # Built output (git ignored)
├── package.json
├── tsconfig.json
├── tsconfig.build.json
└── README.mdLicense
MIT
Contributing
See the main repository CONTRIBUTING.md for contribution guidelines.
Support
For issues and feature requests, please visit the GitHub repository.
