#
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
Use Link Component: Always use the
Link
component for navigation rather than direct URL manipulation.Platform Awareness: Consider platform differences when implementing navigation:
Stack Management: Be mindful of stack depth and use
popN
orsetRoot
to maintain reasonable navigation depth.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.