npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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-events

Quick 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 reads store.routeNode directly)
  • @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
): void

Examples:

// 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
): UseRouteSitemapResult

Options:

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(): UseNavigationStackResult

Result:

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 utilities
  • expo-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 build

Type Checking

pnpm typecheck

Clean Build

pnpm clean

Package 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.md

License

MIT

Contributing

See the main repository CONTRIBUTING.md for contribution guidelines.

Support

For issues and feature requests, please visit the GitHub repository.