TanStack Router Meets NativeScript - A Native Navigation Story
TanStack Router is built around deeply typed navigation. It infers route types all the way through params, search state, and navigation calls, so routes stay consistent as they evolve and the usual "stringly"-typed route bugs mostly disappear. That makes a large codebase safer to refactor, by hand or with AI tooling, and it's now available to NativeScript apps through @nativescript/tanstack-router.
This first implementation targets SolidJS, but the architecture is shaped so other JavaScript frameworks can follow the same path. It's also part one of two: here we cover the native navigation model and the runtime contract, and part two covers file-based routing on top of the same architecture.
- Frame and Page are the Contract
- The Architecture at a Glance
- End-to-End Navigation Flow
- Why Backstack Pages Require Special Care
- Why SolidJS Was a Strong Target
- Built for More Than One Framework
- Shared Element Transitions
- Coming from the Web
- 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, one mental model unlocks everything: Frame is the navigator, and Page is the screen. A Frame holds a real native backstack of Page objects, frame.navigate() pushes, frame.goBack() pops. These aren't simulated route operations. They're native transitions, with native lifecycle and the native expectations users already have, like Android's hardware back button and iOS swipe-back.
That's what makes NativeScript such a strong target for framework interop. The routing adapter's job reduces to four things: listen to router intent, translate it into Frame operations, render framework UI inside Page instances, and dispose those resources when a page leaves the native lifecycle.
The Architecture at a Glance
TanStack Router owns the state layer, typed routes, params and search parsing, loaders, and declarative navigation. NativeScript owns the runtime, the native page stack, transitions, back affordances, and page lifecycle. The provider sits between them, watching router state and translating it into navigation: forward and replace transitions call frame.navigate(...), back transitions call frame.goBack(). Every leaf match renders into its own Page, which keeps route composition explicit and aligned with native stack semantics.
End-to-End Navigation Flow
The payoff is that there's no "fake back" and no browser-emulation layer. The router's idea of the stack and the actual native stack never drift apart.
Why Backstack Pages Require Special Care
One subtle detail needs attention: NativeScript keeps prior pages alive in the backstack even while they aren't visible. So a framework render root isn't always the same thing as an active route match, a hidden page can stay mounted while route matching has moved on.
The integration handles this with lifecycle discipline. Render roots are cleaned up on disposeNativeView, hidden backstack pages stay safe until they're re-activated, and error-boundary resets let page trees recover when they return to the foreground after a stale match.
Why SolidJS Was a Strong Target
SolidJS fits this architecture well. Its fine-grained reactivity keeps route-state updates cheap, its per-page root creation and disposal maps cleanly onto the NativeScript page lifecycle, and its error boundaries make the backstack recovery above straightforward.
Built for More Than One Framework
Underneath, the design is framework-agnostic: a NativeScript-aware navigation and state core, a framework-specific renderer and context layer on top, and a shared set of lifecycle and backstack safety rules. Solid is the first renderer to sit on that core, but React, Vue, Svelte, and others can reuse the same foundation.
Shared Element Transitions
Because NativeScript supports Shared Element Transitions, TanStack Router gets them natively. The pattern stays TanStack-aligned: navigation intent lives in router.navigate(...) options, per-navigation visual metadata rides along in navigation state, and the NativeScript provider reads that metadata when it calls frame.navigate(...). Router semantics stay intact while the platform handles the visuals.
In practice, the source and destination views share a 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 larger apps, pull this into a small utility so tags and transition config stay in one place:
// 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 },
},
},
},
}),
)
}
Coming from the Web
If you already use TanStack Router on the web, your route-level habits carry over. The one shift is what navigation drives: on the web, a URL change updates sections of a single app tree; on NativeScript, router state drives operations on a real native page stack.
5-Minute Getting Started (Solid + TypeScript)
Here's the shortest path to a running app.
1. Create a new NativeScript Solid app
ns create myapp --solid
Choose TypeScript when prompted for a language, 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 (
<gridlayout>
<NativeScriptRouterProvider
router={router}
debug={true}
actionBarVisibility="always"
/>
</gridlayout>
)
}
5. Run and verify navigation
ns run ios
# or
ns run android
Tap Go to About and you should see a native push transition. That's the core adapter loop running end to end: router state changes drive Frame navigation, and each route component renders into its own 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 meant for experimentation, feedback, and collaboration, and not ready for production yet. If the direction is useful for your team, now is the best time to try it and help shape 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
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 native platforms.