Skip to content
cccskills
FreePublished on GitHub

router-core-code-splitting

Automatic code splitting (autoCodeSplitting), .lazy.tsx convention, createLazyFileRoute, createLazyRoute, lazyRouteComponent, getRouteApi for typed hooks in split files, codeSplitGroupings per-route override, splitBehavior programmatic config, critical vs non-critical properties.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md8.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Code Splitting

TanStack Router separates route code into critical (required to match and start loading) and non-critical (can be lazy-loaded). The bundler plugin can split automatically, or you can split manually with .lazy.tsx files.

CRITICAL: Never export component functions from route files — exported functions are included in the main bundle and bypass code splitting entirely.

CRITICAL: Use getRouteApi('/path') in code-split files, NOT import { Route } from './route'. Importing Route defeats code splitting.

What Stays in the Main Bundle (Critical)

  • Path parsing/serialization
  • validateSearch
  • loader, beforeLoad
  • Route context, static data
  • Links, scripts, styles

What Gets Split (Non-Critical)

  • component
  • errorComponent
  • pendingComponent
  • notFoundComponent

The loader is NOT split by default. It is already async, so splitting it adds a double async cost: fetch the chunk, then execute the loader. Only split the loader if you have a specific reason.

Setup: Automatic Code Splitting

Enable autoCodeSplitting: true in the bundler plugin. This is the recommended approach.

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    // TanStack Router plugin MUST come before the framework plugin
    tanstackRouter({
      autoCodeSplitting: true,
    }),
    react(),
  ],
})

With this enabled, route files are automatically transformed. Components are split into separate chunks; loaders stay in the main bundle. No .lazy.tsx files needed.

// src/routes/posts.tsx — everything in one file, splitting is automatic
import { createFileRoute } from '@tanstack/react-router'
import { fetchPosts } from '../api'

export const Route = createFileRoute('/posts')({
  loader: fetchPosts,
  component: PostsComponent,
})

// NOT exported — this is critical for automatic code splitting to work
function PostsComponent() {
  const posts = Route.useLoaderData()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Manual Splitting with .lazy.tsx

If you cannot use automatic code splitting (e.g. CLI-only, no bundler plugin), split manually into two files:

// src/routes/posts.tsx — critical route config only
import { createFileRoute } from '@tanstack/react-router'
import { fetchPosts } from '../api'

export const Route = createFileRoute('/posts')({
  loader: fetchPosts,
})
// src/routes/posts.lazy.tsx — non-critical (lazy-loaded)
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/posts')({
  component: PostsComponent,
})

function PostsComponent() {
  // Use getRouteApi to access typed hooks without importing Route
  return <div>Posts</div>
}

createLazyFileRoute supports only: component, errorComponent, pendingComponent, notFoundComponent.

Virtual Routes

If splitting leaves the critical route file empty, delete it entirely. A virtual route is auto-generated in routeTree.gen.ts:

// src/routes/about.lazy.tsx — no about.tsx needed
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/about')({
  component: () => <h1>About Us</h1>,
})

Code-Based Splitting

For code-based (non-file-based) routing, use createLazyRoute and the .lazy() method:

// src/posts.lazy.tsx
import { createLazyRoute } from '@tanstack/react-router'

export const Route = createLazyRoute('/posts')({
  component: PostsComponent,
})

function PostsComponent() {
  return <div>Posts</div>
}
// src/app.tsx
import { createRoute } from '@tanstack/react-router'

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
}).lazy(() => import('./posts.lazy').then((d) => d.Route))

Accessing Typed Hooks in Split Files: getRouteApi

When your component lives in a separate file, use getRouteApi to get typed access to route hooks without importing the Route object:

// src/routes/posts.lazy.tsx
import { createLazyFileRoute, getRouteApi } from '@tanstack/react-router'

const routeApi = getRouteApi('/posts')

export const Route = createLazyFileRoute('/posts')({
  component: PostsComponent,
})

function PostsComponent() {
  const posts = routeApi.useLoaderData()
  const { page } = routeApi.useSearch()
  const params = routeApi.useParams()
  const context = routeApi.useRouteContext()
  return <div>Posts page {page}</div>
}

getRouteApi provides: useLoaderData, useLoaderDeps, useMatch, useParams, useRouteContext, useSearch.

Per-Route Split Overrides: codeSplitGroupings

Override split behavior for a specific route by adding codeSplitGroupings directly in the route file:

// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/react-router'
import { loadPostsData } from './-heavy-posts-utils'

export const Route = createFileRoute('/posts')({
  // Bundle loader and component together for this route
  codeSplitGroupings: [['loader', 'component']],
  loader: () => loadPostsData(),
  component: PostsComponent,
})

function PostsComponent() {
  const data = Route.useLoaderData()
  return <div>{data.title}</div>
}

Global Split Configuration

defaultBehavior — Change Default Groupings

// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        defaultBehavior: [
          // Bundle all UI components into one chunk
          [
            'component',
            'pendingComponent',
            'errorComponent',
            'notFoundComponent',
          ],
        ],
      },
    }),
  ],
})

splitBehavior — Programmatic Per-Route Logic

// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        splitBehavior: ({ routeId }) => {
          if (routeId.startsWith('/posts')) {
            return [['loader', 'component']]
          }
          // All other routes use defaultBehavior
        },
      },
    }),
  ],
})

Precedence Order

  1. Per-route codeSplitGroupings (highest)
  2. splitBehavior function
  3. defaultBehavior option (lowest)

Common Mistakes

1. HIGH: Exporting component functions prevents code splitting

// WRONG — export puts PostsComponent in the main bundle
export function PostsComponent() {
  return <div>Posts</div>
}

// CORRECT — no export, function stays in the split chunk
function PostsComponent() {
  return <div>Posts</div>
}

2. MEDIUM: Trying to code-split the root route

__root.tsx does not support code splitting. It is always rendered regardless of the current route. Do not create __root.lazy.tsx.

3. MEDIUM: Splitting the loader adds double async cost

// AVOID unless you have a specific reason
codeSplittingOptions: {
  defaultBehavior: [
    ['loader'], // Fetch chunk THEN execute loader = two network waterfalls
    ['component'],
  ],
}

// PREFERRED — loader stays in main bundle (default behavior)
codeSplittingOptions: {
  defaultBehavior: [
    ['component'],
    ['errorComponent'],
    ['notFoundComponent'],
  ],
}

4. HIGH: Importing Route in code-split files for typed hooks

// WRONG — importing Route pulls route config into the lazy chunk
import { Route } from './posts.tsx'
const data = Route.useLoaderData()

// CORRECT — getRouteApi gives typed hooks without pulling in the route
import { getRouteApi } from '@tanstack/react-router'
const routeApi = getRouteApi('/posts')
const data = routeApi.useLoaderData()

Cross-References

  • router-core-data-loading — Loader splitting decisions affect data loading performance. Splitting the loader adds latency before data can be fetched.
  • router-core-type-safety — getRouteApi is the type-safe way to access hooks from split files.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Build AI chat interfaces using ai-elements components — conversations, messages, tool displays, prompt inputs, and more. Use when the user wants to build a chatbot, AI assistant UI, or any AI-powered chat interface.

The English summary is being prepared. Showing the original description.

hashintel/brunch8Updated Oct 17, 2026

Search the live web via Perplexity Search API. Use when you need current documentation, release notes, vendor pages, news, domain-constrained web search, or date/recency filtering. Not for local codebase search or stable docs already in context.

The English summary is being prepared. Showing the original description.

hashintel/brunch8Updated Oct 17, 2026

Chrome DevTools CLI for browser automation via shell commands. Use when interacting with web pages from the command line — navigating, clicking, filling forms, inspecting console/network, taking screenshots, or extracting page content. Triggers on: browse a page, automate Chrome, inspect console, check network requests, take a screenshot, fill a form, click a button.

The English summary is being prepared. Showing the original description.

hashintel/brunch8Updated Oct 17, 2026

Uses the chrome-devtools-axi CLI for browser automation, accessibility-tree snapshots, console and network inspection, screenshots, Lighthouse audits, and performance traces. Use when interacting with Chrome from the shell, especially when the user mentions chrome-devtools-axi, AX snapshots, browser debugging, or DevTools automation from the command line.

The English summary is being prepared. Showing the original description.

hashintel/brunch8Updated Oct 17, 2026

Deep expertise in cmux — the terminal multiplexer with native browser views. Use when managing panes, reading terminal output, sending keystrokes, opening browser views, or manually testing web UIs and TUIs inside cmux. Triggers on: cmux, open a browser pane, split terminal, read screen, send keys, test this UI in cmux, preview in cmux.

The English summary is being prepared. Showing the original description.

hashintel/brunch8Updated Oct 17, 2026

Uses the gh-axi CLI for GitHub shell operations: issue, pull request, workflow run, release, repo, search, and API tasks. Prefer this over regular `gh` for GitHub reads and simple mutations when an agent needs compact, structured, suggestion-rich output. Triggers on: gh, GitHub CLI, github issue, github pr, pull request, workflow run, github release, gh api, repo inspection, list PRs, view issue, check workflow runs, inspect repo, GitHub shell operations.

The English summary is being prepared. Showing the original description.

hashintel/brunch8Updated Oct 17, 2026

See all skills by hashintel

Report a problem with this skill