# Polycentric's Custom Router

# Overview

Polycentric uses a custom stack-based router instead of traditional react router like the eponymous React Router. This router maintains a history stack and provides navigation controls like swipe-back gestures and transitions similar to native mobile apps, while still supporting web browser history and URL sharing.

The router was designed at the time to provide the most native-like PWA experience possible, and may still be.

# Why a Custom Router?

# 1. Native-like Navigation

The stack router was built to provide a more native-like navigation experience, especially on mobile devices. It manages a stack of pages that can be pushed and popped, similar to how iOS and Android handle navigation.

# 2. Integration with Ionic

The router integrates with Ionic's IonNav component to provide native-like swipe-back gestures and transitions. This is achieved by managing both our virtual navigation stack and Ionic's physical view stack. We have to do some approximation to determine how much to pop off the stack in the case of a user pops off many pages on desktop, but in most cases, this is a very good experience.

# 3. Platform-Specific Routing

The router uses different strategies for mobile and desktop:

# Mobile (Memory Router)

  • Uses in-memory routing to avoid conflicts with browser gestures
  • Maintains virtual navigation stack separate from browser history
  • Relies on Ionic's native-like gestures for navigation

# Desktop (Browser Router)

  • Integrates with browser history API
  • Supports browser back/forward buttons
  • Maintains shareable URLs

# 4. View Persistence

Since we have our own data layer producing feeds, it's easier and more performant to store such feeds unrendered in the background than reconstruct them on frame 1 of every navigation.

# Core Components

# 1. Route Configuration

Routes are defined in a central configuration object that maps URL patterns to components:

export const routeData: RouteData = {
    '/': { component: HomeFeedPage, root: true },
    '/user/:urlInfoString': { component: UserFeedPage },
    '/settings': { component: SettingsPage },
    // ...
}

# 2. Stack Router Context

The router provides a context that exposes navigation methods and state:

interface StackRouterContextType {
    history: string[];          // Stack of page URLs
    currentPath: string;        // Current page URL
    index: number;             // Current position in stack
    push: (path: string) => void;
    pop: () => void;
    setRoot: (path: string, direction: 'forwards' | 'backwards' | 'inplace') => void;
    popN: (n: number) => void;
    getIonicStackHeight: () => number;
    canGoBack: () => boolean;
}

# Browser History Integration

The router handles browser history events to maintain sync between the browser's history stack and our virtual stack:

# Memory Routing Component

The MemoryRoutedComponent handles rendering pages within Ionic's view stack:

# State Persistence

The router maintains its state in session storage to support:

  • Page refresh recovery
  • Multi-tab navigation
  • Browser history restoration

# Best Practices

  1. Use Link Component: Always use the Link component for navigation rather than direct URL manipulation.

  2. Platform Awareness: Consider platform differences when implementing navigation:

  3. Stack Management: Be mindful of stack depth and use popN or setRoot to maintain reasonable navigation depth.

  4. URL Parameters: Use the useParams hook to access URL parameters in components:

    const { urlInfoString } = useParams<{ urlInfoString: string }>();

This custom router provides a robust foundation for Polycentric's navigation needs while maintaining compatibility with both web and native-like navigation patterns.