URL State Synchronization
Relevant source files
The following files were used as context for generating this wiki page:
URL State Synchronization is a core feature of Better Tables that ensures the internal state of a table—including filters, sorting, and pagination—is bidirectionally synced with the browser's URL. This mechanism enables deep-linking, allowing users to share specific table views or return to their exact state after a page refresh.
The system relies on a serialization layer that compresses complex state objects into URL-safe strings using versioned prefixes, and a React hook layer that manages the lifecycle of this synchronization, including debouncing updates to prevent excessive browser history entries.
Sources: plans/016-filternode-state-layer.md:21-25, packages/ui/src/hooks/use-table-url-sync.ts:1-10
Architecture and Data Flow
The synchronization process involves three primary components: the Table Store (state management), the URL Serialization utility (data transformation), and the useTableUrlSync hook (lifecycle orchestration).
Synchronization Lifecycle
- Hydration: On mount, the hook reads the current URL, deserializes the state, and applies it to the Table Store.
- Subscription: The hook subscribes to state changes in the Table Store.
- Serialization & Update: When state changes (e.g., a new filter is added), the hook serializes the new state and updates the browser URL via a debounced transition.
The diagram shows the bidirectional flow between the browser URL and the internal Table Store, mediated by the sync hook.
Sources: packages/ui/src/hooks/use-table-url-sync.ts:80-100, plans/010-ui-hooks-correctness.md:38-45
Serialization and Wire Formats
Better Tables uses versioned prefixes to manage backward compatibility and evolving state requirements. The serialization process often includes key-shortening and compression to keep URLs within browser length limits.
Wire Format Versions
- Legacy (
c:): The initial flat wire format forFilterState[]. It assumes implicitANDlogic for all filters. - Modern (
c2:): A versioned format introduced to supportFilterGroupNodetrees, enabling complex boolean logic (AND/OR).
Sources: plans/016-filternode-state-layer.md:21-25, plans/016-filternode-state-layer.md:78-85
Key Compression Mapping
To reduce URL length, internal state keys are mapped to single-character short codes during serialization.
| Original Key | Short Code | Context |
|---|---|---|
columnId | c | Filters/Sorting |
type | t | Filter definition |
operator | o | Filter logic |
values | v | Filter data |
kind | k | FilterNode (Group vs Leaf) |
logic | l | Boolean logic (AND/OR) |
Sources: plans/006-core-contract-v2-design.md:43-46, plans/016-filternode-state-layer.md:95-102
Component Roles
useTableUrlSync Hook
This React hook is the primary entry point for consumers. It handles the complexities of React's lifecycle to ensure state is not lost or redundantly updated. Key features include:
- Debouncing: State updates to the URL are debounced (typically by 150ms-300ms) to prevent performance issues and history bloat during rapid typing.
- Hydration Ordering: It manages a "late store hydration" strategy to ensure that if the URL sync hook mounts before the store is initialized, the state is correctly applied once the store becomes available.
- Stability: Uses stable references for configurations to prevent re-subscription loops.
Sources: packages/ui/src/hooks/use-table-url-sync.ts:20-33, plans/010-ui-hooks-correctness.md:48-55, plans/041-client-render-perf.md:120-135
URL Sync Adapter
The url-sync-adapter.ts provides an abstraction layer over different routing environments (e.g., Next.js useRouter vs. vanilla browser window.history).
This sequence illustrates how a user action eventually triggers a URL update via the sync adapter.
Sources: packages/core/src/stores/url-sync-adapter.ts:5-20, packages/ui/src/hooks/use-table-url-sync.ts:180-210
Implementation Details
Filter Tree Serialization
When serializing FilterNode trees (introduced in Core Contract v2), the system performs validation to ensure the resulting URL is safe. This includes:
- Depth Capping: Trees are typically capped at a depth of 3 to avoid excessive URL length and SQL complexity.
- Normalization: Empty groups are dropped, and single-child groups are unwrapped before serialization.
- Fail-Closed Validation: If a
c2:payload is malformed, the system falls back to legacy parsing or drops invalid nodes rather than crashing the application.
Sources: plans/016-filternode-state-layer.md:87-93, plans/006-core-contract-v2-design.md:37-41
Configuration Options
The synchronization behavior can be adjusted through a UrlSyncConfig object.
| Option | Type | Description |
|---|---|---|
enabled | boolean | Global toggle for URL sync functionality. |
debounceMs | number | Time to wait before pushing state changes to the URL. |
replace | boolean | Whether to use history.replaceState instead of pushState. |
tableId | string | Unique identifier used to namespace state in the URL. |
Sources: packages/ui/src/hooks/use-table-url-sync.ts:40-60, plans/010-ui-hooks-correctness.md:120-125
Conclusion
URL State Synchronization in Better Tables provides a robust mechanism for maintaining table state across sessions and shares. By combining a versioned serialization layer with a lifecycle-aware React hook, the system ensures high performance and data integrity while supporting complex features like nested AND/OR filter groups.