npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tanstack-router-sfc

v1.1.0

Published

TanStack Router for Vue with single-file routes: define the route in a <router lang="ts"> block inside the .vue file that renders it.

Readme

tanstack-router-sfc

CI npm

TanStack Router for Vue where one .vue file is the whole route: the route definition lives in a <router lang="ts"> custom block next to the component that renders it.

<template>
	<article>{{ post.title }}</article>
</template>

<script setup lang="ts">
// `Route` is in scope here - no import.
const post = Route.useLoaderData()
</script>

<router lang="ts">
import { createFileRoute, notFound } from '@tanstack/vue-router'
import { getPost } from '@/lib/posts'

export default createFileRoute('/posts/$postId')({
	loader: async ({ params }) => {
		const post = await getPost(params.postId)
		if (!post) throw notFound()
		return post
	}
})
</router>

This replaces the x.ts + x.component.vue pair. Both conventions work in the same routes directory, so migration can be gradual.

Setup

// vite.config.ts
import { tanstackRouterSfc } from 'tanstack-router-sfc'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
	plugins: [...tanstackRouterSfc(), vue()]
})
// tsconfig.json - typechecking and IDE support for the block
{
	"vueCompilerOptions": {
		"plugins": ["tanstack-router-sfc/volar"]
	}
}

tanstackRouterSfc wraps tanstackRouter from @tanstack/router-plugin, so it takes the same options (routesDirectory, generatedRouteTree, autoCodeSplitting, codeSplittingOptions, plugins, ...) plus blockType (default 'router'). target and addExtensions are managed for you.

Peer dependencies: vite, vue, @tanstack/router-plugin, @tanstack/vue-router.

Rules

  • The block must be <router lang="ts"> and must export default createFileRoute('<path>')({ ... }) - exactly one, with a string-literal path. __root.vue uses createRootRoute* instead.
  • A stale path after a rename is fixed in place by the generator, the same way it fixes .ts route files.
  • Route is available in <script setup> without an import. It is not available in <template> (it is a <script> binding, like any plain <script> value) - assign it to a const in setup if the template needs it.
  • An SFC with a <template> or <script setup> becomes the route's component, loaded lazily. A block-only .vue (redirect or pathless layout) gets no component.
  • Piece files (x.component.vue, x.errorComponent.vue, x.lazy.tsx, ...) and plain .ts route files are untouched.
  • Every non-piece .vue file under the routes directory must have a block. Use the routeFileIgnorePrefix (-) for helper files.

How it works

Route generation, file watching, formatting, code splitting and route HMR are the official tanstackRouter plugin's job - this composes it with addExtensions: true, so the generated tree imports ./routes/x.vue and TypeScript can see the block's default-exported route as Route. On top of that:

  1. a generator plugin (afterTransform) requires every single-file route to have a block, keeps its createFileRoute('...') id in sync when the file moves, and scaffolds an empty .vue as an SFC;
  2. :route-tree rewrites the tree's ./routes/x.vue imports at transform time to x.vue.tsr-router.ts - the block alone - so the route tree never pulls a component into the entry chunk;
  3. :sfc replaces the <router> block with a <script> shim that imports the default-exported route as Route from that module, preserving the file's line count so template and setup diagnostics stay accurate;
  4. :router-block serves x.vue.tsr-router.ts: the block's TypeScript, padded to the original line numbers, plus Route.update({ component: lazyRouteComponent(() => import('./x.vue')) }).

volar.cjs splices the block into the SFC's generated TypeScript so vue-tsc (and golar, and the VS Code Vue extension) typecheck it in place.

HMR

Editing <template> / <script setup> / <style> hot-updates as usual. Editing the <router> block triggers a full reload - the router is built once at startup, so a changed route definition cannot be swapped in place.

Note on autoCodeSplitting

Single-file routes are split by construction, so you do not need it for them. It is passed through to the official plugin unchanged, which is what still splits inline component: options in .ts route files.

App kit: tanstack-router-sfc/app

Runtime logic for Vue apps on TanStack Router with Nuxt UI. It ships no components: the app keeps its design-system shells and wires them to these. Every function that navigates uses useRouter() from the component it runs in, or takes a router.

| Area | Exports | |---|---| | Nuxt UI on TanStack Router | vueRouterShimOptions(history) - app.use(createRouter(vueRouterShimOptions(createWebHistory()))) | | URL search state | useQueryParam, useRouteSearchModels, useRouteSearchSync, useRouterPush, searchFlag, defineStaticData | | Route loading | routeLoadingMode, trackRouteLoading(router) | | Dialogs opened by URL | createDialogs({ dialogs, host, useOverlay, confirm }) -> useOpenDialog, useConfirmAction, useDialogDeepLink; useDialogHost for the host component | | Maintenance / API down | createMaintenanceMode({ probe }), parseApiError | | Forms | formStateFromSchema, useEntityForm, useFormSubmit, useSettingsFormSection, provideSaveScope / useSaveSection, useFormFieldModel, useFieldHighlight, formStateKey, formIdKey |

Toasts and translations are the app's: provide them once.

app.provide(formFeedbackKey, () => {
	const toast = useToast()
	const { t } = useI18n()
	return {
		success: (title) => toast.add({ color: 'success', title }),
		error: (error, title) =>
			toast.add({ color: 'error', title: title ?? t('saveFailed') })
	}
})

vue-router (the shim) and zod (formStateFromSchema types) are optional peer dependencies; the kit imports neither at runtime.

Development

bun run test        # unit tests + a real `vite build` of test/fixture
bun run typecheck
bun run build       # tsdown -> dist/

Releasing

Squash PRs with conventional titles (fix:, feat:, or feat!:). Release Please keeps the version and changelog in a release PR; merge that PR to publish with release notes and npm provenance. See release and recovery instructions.