tanstart-analyzer
v0.2.0
Published
Static analyzer for TanStack Start apps - detect server code leaking into client bundles
Maintainers
Readme
tanstart-analyzer
Static analyzer for TanStack Start apps. Detects patterns that cause server code to leak into client bundles.
Why?
TanStack Start route loaders are isomorphic - they run on both server AND client during SPA navigation. If you import server-only modules (database, secrets) in loaders or components, they get bundled into the client.
This analyzer detects these patterns before they become production bugs.
Installation
npm install -D tanstart-analyzer
# or
pnpm add -D tanstart-analyzerQuick Start
# Create config file
npx tanstart-analyzer init
# Run analysis
npx tanstart-analyzer checkRules
| Rule | Severity | Description |
|------|----------|-------------|
| direct-db-import | error | Server module imports in isomorphic files |
| loader-server-import | error | Server imports used in route loaders |
| server-fn-misuse | error | Server functions called at module level |
| barrel-export | warn | export * patterns that prevent tree-shaking |
| env-leak | error | process.env access in client code |
| import-chain | error | Transitive imports reaching server modules |
Configuration
Create tanstart-analyzer.config.ts:
export default {
rootDir: "./src",
boundaries: {
// Files that ONLY run on server
serverOnlyPatterns: [
"**/server/**/*.ts",
"**/inngest/**/*.ts",
],
// Files bundled for BOTH server and client
isomorphicPatterns: [
"**/routes/**/*.tsx",
"**/components/**/*.tsx",
"**/hooks/**/*.ts",
"**/lib/**/*.ts",
],
// API routes (server-only)
serverApiPatterns: ["**/routes/api/**/*.ts"],
},
// Modules that should NEVER be imported in isomorphic files
serverOnlyModules: [
"postgres",
"drizzle-orm/postgres-js",
"~/server/functions/db",
],
rules: {
"direct-db-import": "error",
"loader-server-import": "error",
"server-fn-misuse": "error",
"barrel-export": "warn",
"env-leak": "error",
"import-chain": "error",
},
}CLI Usage
# Run with default config
npx tanstart-analyzer check
# Use specific config file
npx tanstart-analyzer check --config ./my-config.ts
# Output as JSON
npx tanstart-analyzer check --format json
# Write output to file
npx tanstart-analyzer check --format json --output report.json
# Show verbose output (import chains)
npx tanstart-analyzer check --verbose
# Fail on warnings
npx tanstart-analyzer check --fail-on-warnProgrammatic API
import { analyze, loadConfig } from "tanstart-analyzer"
// Simple usage
const result = await analyze()
if (result.errorCount > 0) {
console.error(`Found ${result.errorCount} errors`)
for (const violation of result.violations) {
console.log(`${violation.relativePath}:${violation.line} - ${violation.message}`)
}
process.exit(1)
}
// With custom config
const config = await loadConfig({ configPath: "./custom-config.ts" })
const result = await analyze({ config })How It Works
File Classification
The analyzer classifies files based on patterns:
- server-only: Files in
server/orinngest/directories - safe to import database - server-api: API routes with
server.handlers- server-only execution - isomorphic: Routes, components, hooks - bundled for BOTH server and client
What Gets Detected
Direct Server Imports
// ❌ BAD - db imported in component import { db } from "~/server/functions/db"Loader Server Imports
// ❌ BAD - db used in loader (runs on client!) import { db } from "~/server/functions/db" export const Route = createFileRoute('/trips')({ loader: async () => await db.select().from(trips) })Environment Variable Leaks
// ❌ BAD - secret exposed to client const secret = process.env.SECRET_KEYImport Chains
// ❌ BAD - transitive import reaches server module // component.tsx → utils.ts → helpers.ts → db.ts
Safe Patterns
Type-only imports are always safe:
import type { TripOffer } from "@tourvy/database"Server functions are safe (TanStack Start handles code-splitting):
import { getTrips } from "~/server/functions/trips" export const Route = createFileRoute('/trips')({ loader: async () => await getTrips({ data: {} }) // RPC call })Guarded env access is safe:
if (typeof window !== "undefined") { return { apiUrl: "/api" } } return { apiUrl: process.env.API_URL }
CI Integration
Add to your CI pipeline:
# .github/workflows/ci.yml
- name: Check bundle
run: npx tanstart-analyzer checkLicense
MIT
