TanStack / TanStack/router

[Start] Dev server breaks on IP/Safari: Nitro intercepts $id route modules when Sec-Fetch-Dest header is missing

Open
#7,095 2 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

Description

When accessing a TanStack Start app via IP address (e.g. http://192.168.0.64:3000) or Safari, route files containing $ (like $id.edit.tsx) fail to load as JS modules. Nitro's dev middleware intercepts these requests and interprets $id as a route parameter instead of letting Vite serve them.

This effectively breaks Vite's server.host: true option for TanStack Start projects.

Reproduction

  1. Create a TanStack Start project with dynamic route files (e.g. routes/_authed/manager/$id.edit.tsx)
  2. Set server.host: true in vite.config.ts
  3. Access the app via IP address or Safari
Expected

Modules load correctly, same as Chrome on localhost.

Actual
GET http://192.168.0.64:3000/app/routes/_authed/manager/$id.edit.tsx
→ 307 redirect to /app/routes/_authed/manager/undefined
→ 404

The app fails to hydrate because the route modules can't be loaded.

Root cause

Nitro's nitroDevMiddlewarePre middleware decides whether to intercept a request based on the Sec-Fetch-Dest header:

// nitro/dist/_chunks/plugin.mjs
if (!fetchDest || /^(document|iframe|frame|empty)$/.test(fetchDest)) {
    nitroDevMiddleware(req, res, next);  // Nitro handles
} else {
    next();  // Vite handles
}

Browsers don't always send this header:

Browser localhost IP address
Chrome script missing
Safari missing missing

Verified by logging incoming headers on a standalone HTTP server.

When the header is missing, Nitro intercepts the module request. Since the URL contains $id (TanStack Router's filename convention for dynamic params), Nitro's routing resolves $id to undefined and returns a 307 redirect.

On localhost with Chrome, Sec-Fetch-Dest: script is sent, so Nitro skips the request and Vite serves the module correctly.

Additional issue: Secure cookie on HTTP

useSession() sets the Secure flag on the session cookie by default. This prevents the cookie from being stored on HTTP connections (IP access). Chrome makes an exception for localhost, but Safari and IP access don't work.

This means even after fixing the module loading issue, login doesn't work on IP/Safari because the session cookie is rejected by the browser.

Workaround:

useSession<SessionData>({
  password: process.env.SESSION_SECRET!,
  cookie: {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
  },
})

Workaround for module loading

A Vite plugin that injects the missing header before Nitro's middleware:

import { type Plugin } from 'vite'

function patchSecFetchDest(): Plugin {
  return {
    name: 'patch-sec-fetch-dest',
    configureServer(server) {
      server.middlewares.use((req, _res, next) => {
        if (!req.headers['sec-fetch-dest'] && req.url && /\.[mc]?[jt]sx?(\?|$)/.test(req.url)) {
          req.headers['sec-fetch-dest'] = 'script'
        }
        next()
      })
    },
  }
}

Environment

  • TanStack Start: v1.167+
  • Nitro: v3 (via nitro/vite)
  • Vite: v8.0.3
  • macOS, tested with Chrome, Safari, Playwright (Chromium headless + WebKit)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with nitro/dist/_chunks/plugin.mjs and the nitroDevMiddlewarePre logic described in the issue, then inspect vite.config.ts and the middleware ordering in a reproduced TanStack Start app. Use the listed dynamic route and server.host: true reproduction on an IP address and Safari or WebKit; done means route modules load without the redirect and the reported IP/Safari behavior is verified.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript, vite
Domain
tooling, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.