TanStack Router Meets NativeScript - A Native Navigation Story
Imagine type-safe navigation across your whole app no matter the framework or target platform. You can have it today with TanStack Router, which is built to infer route types deeply, so params, search state, and navigation calls stay consistent as routes evolve. That means fewer broken links, fewer stringly-typed route bugs, and better refactors in large NativeScript apps.
There is also a nice benefit around URL-like state even on native. TanStack Router treats search params as a first-class state layer. On mobile, that does not have to mean browser URLs only. It can mean "serializable, shareable, debuggable navigation state" for filters, tabs, pagination, sort, selected item IDs, and modal state. That is useful for deep links, app restoration, analytics, and reproducible bug reports. Plus you can have all that while also having truly native iOS and Android navigation, including stack transitions, hardware back, and gesture-driven back.
@nativescript/tanstack-router achieves this by keeping navigation native to the target platform, while using TanStack Router as the typed, predictable control plane. The first implementation is SolidJS focused, and the architecture is intentionally shaped so other JavaScript frameworks can follow the same path.
- Frame and Page are the Contract
- The Architecture at a Glance
- How TanStack Router Connects to NativeScript
- End-to-End Navigation Flow
- Why Backstack Pages Require Special Care
- Why SolidJS Was a Strong Target
- Designed for More Than One Framework
- Shared Element Transitions
- What it means for TanStack Router
- 5-Minute Getting Started (Solid + TypeScript)
- Status: Early Developer Preview
- Sample Project
- References
- Help Accelerate the Work
Frame and Page are the Contract
If you build framework integrations for NativeScript, there is one mental model that unlocks everything:
A Frame maintains a real native backstack of Page objects. frame.navigate() pushes. frame.goBack() pops. Those are not simulated route operations. They are native transitions with native lifecycle and native user expectations attached.
This is why NativeScript is such a strong target for JS framework interop. The platform gives you a clear primitive contract:
- Navigation state machine: Frame
- Visual/lifecycle boundary: Page
- Platform behaviors: Android hardware back and iOS swipe-back
For any JavaScript framework, the routing adapter job is then explicit:
- Listen to router intent.
- Translate that intent into Frame operations.
- Render framework UI inside Page instances.
- Dispose framework resources when pages leave the native lifecycle.
The Architecture at a Glance
TanStack Router remains the source of route truth. NativeScript remains the source of navigation runtime truth.
The implementation does the translation between those two truths.
How TanStack Router Connects to NativeScript
TanStack Router provides a mature state layer:
- Type-safe routes
- Params and search parsing
- Loader execution
- Declarative navigation APIs
NativeScript provides a mature runtime navigation layer:
- Native page stack
- Native transition behavior
- Native back affordances
- Page lifecycle events
In this implementation, the provider watches router state and makes directional decisions:
- Forward/replace transitions map to
frame.navigate(...) - Back transitions map to
frame.goBack()
Every leaf match is rendered into its own Page. That keeps route composition explicit and aligned with native stack semantics.
End-to-End Navigation Flow
This is the most important integration quality: no "fake back," no browser emulation layer, no mismatch between what the router believes and what the native stack is doing.
Why Backstack Pages Require Special Care
A subtle but critical detail: NativeScript can keep prior pages alive in the backstack while they are not visible.
That means framework render roots are not always equivalent to active route matches. A hidden page may still be mounted while route matching has moved elsewhere.
This implementation addresses that reality with lifecycle discipline:
- Page render roots are cleaned up on
disposeNativeView. - Hidden backstack pages stay safe until re-activation.
- Error-boundary reset behavior helps page trees recover when returning to foreground after stale-match conditions.
Why SolidJS Was a Strong Target
SolidJS gives this architecture practical advantages:
- Fine-grained reactivity keeps route-state updates efficient.
- Per-page root creation/disposal maps well to NativeScript page lifecycle.
- Error boundary patterns are straightforward for backstack recovery scenarios.
Designed for More Than One Framework
This implementation is a framework-agnostic routing design matching TanStack's principles.
The reusable architecture is:
- NativeScript-aware navigation/state core
- Framework-specific renderer/context layer
- Shared lifecycle and backstack safety rules
Shared Element Transitions
Because NativeScript supports Shared Element Transitions, TanStack Router gets them for free here.
The implementation follows a TanStack-aligned pattern:
- Keep navigation intent in
router.navigate(...)options. - Carry per-navigation visual metadata through navigation
state. - Let the NativeScript provider consume that metadata when calling
frame.navigate(...).
This keeps TanStack Router semantics intact while allowing platform-specific visual behavior in NativeScript.
In practice, the source and destination views use a matching sharedTransitionTag, and the Link sets a per-navigation transition:
import { Link, createNativeScriptTransitionState } from '@nativescript/tanstack-router/solid'
import { PageTransition, SharedTransition } from '@nativescript/core'
const avatarTag = `post-author-avatar-${authorId}`
<Link
to={`/posts/${postId}`}
state={createNativeScriptTransitionState(
SharedTransition.custom(new PageTransition(), {
pageEnd: {
sharedTransitionTags: {
[avatarTag]: {
scale: { x: 0.9, y: 0.9 },
},
},
},
}),
)}
>
<image sharedTransitionTag={avatarTag} class="w-[28] h-[28] rounded-full" />
</Link>
// On destination page
<image sharedTransitionTag={avatarTag} class="w-[72] h-[72] rounded-full" />
For teams already using TanStack Router, this should feel familiar: route state remains the extensibility point, while runtime adapters decide how to interpret it.
For larger apps, we recommend extracting this into a small utility so tags and transition config stay centralized:
// src/utils/shared-transitions.ts
import { createNativeScriptTransitionState } from '@nativescript/tanstack-router/solid'
import { PageTransition, SharedTransition } from '@nativescript/core'
export function getPostAuthorAvatarTag(authorId: string): string {
return `post-author-avatar-${authorId}`
}
export function createPostAuthorSharedTransitionState(authorId: string) {
return createNativeScriptTransitionState(
SharedTransition.custom(new PageTransition(), {
pageEnd: {
sharedTransitionTags: {
[getPostAuthorAvatarTag(authorId)]: {
scale: { x: 0.9, y: 0.9 },
},
},
},
}),
)
}
What it means for TanStack Router
If you already use TanStack Router on the web, most of your route-level habits carry over.
The conceptual shift is simply this:
- On web: URL updates drive sections of one app tree.
- On NativeScript: router state drives native page stack operations.
Same routing confidence, different runtime expression.
5-Minute Getting Started (Solid + TypeScript)
If you want to try this quickly, here is the shortest practical path.
1. Create a new NativeScript Solid app
ns create myapp --solid
When prompted for language, choose TypeScript.
Then open the project:
cd myapp
2. Install TanStack Router integration packages
npm install @nativescript/tanstack-router @tanstack/solid-router @tanstack/history
3. Define a tiny route tree
Create src/routes.tsx:
import { createRootRoute, createRoute } from '@nativescript/tanstack-router/solid'
import { Link } from '@nativescript/tanstack-router/solid'
function Home() {
return (
<stackLayout>
<label text="Home" />
<Link to="/about">
<label text="Go to About" />
</Link>
</stackLayout>
)
}
function About() {
return (
<stackLayout>
<label text="About" />
</stackLayout>
)
}
const rootRoute = createRootRoute()
const homeRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: Home,
})
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/about',
component: About,
})
export const routeTree = rootRoute.addChildren([homeRoute, aboutRoute])
4. Create router and provider
Update src/app.tsx:
import {
NativeScriptRouterProvider,
createNativeScriptRouter,
} from '@nativescript/tanstack-router/solid'
import { routeTree } from './routes'
const router = createNativeScriptRouter({
routeTree,
initialPath: '/',
})
declare module '@nativescript/tanstack-router/solid' {
interface Register {
router: typeof router
}
}
export function App() {
return <NativeScriptRouterProvider router={router} actionBarVisibility="always" />
}
5. Run and verify navigation
ns run ios
# or
ns run android
Tap Go to About and verify a native push transition occurs.
At this point, you have the essential adapter loop running:
- TanStack Router state changes
- NativeScript
Framenavigation actions - Route components rendered per native
Page
From here, you can layer in typed params, loaders, and Link back behavior.
Status: Early Developer Preview
This is an Early Developer Preview.
It is for experimentation, feedback, and collaboration.
It is not intended for production use at this stage.
If the direction is useful for your team, now is the best time to explore and influence what a production-ready cross-framework version should become.
Sample Project
The sample project for this implementation can be found here.
References
Help Accelerate the Work
If you want to help move this effort forward, sponsorship directly increases the time and focus available for implementation quality, test coverage, and framework expansion.
- Open Collective: https://opencollective.com/nativescript
- GitHub Sponsors: https://github.com/sponsors/NativeScript
If enough teams rally around this, TanStack-style routing with first-class native stack behavior can become a durable foundation for JavaScript apps on mobile.