This document analyzes the migration from TanStack Start (apps/ui
) to Next.js (apps/next
) and examines whether the performance improvements were purely due to the framework switch or additional optimizations made during the migration.
The migration produced significant performance improvements:
Metric | TanStack Start | Next.js | Improvement |
---|---|---|---|
Performance | 85 | ✅ 100 | +15 points |
Accessibility | 85 | 84 | -1 point |
Best Practices | 74 | 78 | +4 points |
SEO | 80 | ✅ 100 | +20 points |
FCP | 0.3s | 0.3s | No change |
Speed Index | ❌ 2.4s | ✅ 1.1s | 54% faster |
TBT | 50ms | 60ms | +10ms |
CLS | 0 | 0 | No change |
Our analysis reveals that significant optimizations were implemented during the migration beyond just changing frameworks. Here are the specific improvements:
Major Performance Impact on Speed Index
// Regular img tags - no optimization
<img
src={heroImageDark}
alt="background"
width="3276"
height="4095"
/>
import Image from "next/image";
<Image
src="/new-hero.png"
alt="app screen"
width={2696}
height={1386}
/>
Impact: This single change likely accounts for the dramatic Speed Index improvement (2.4s → 1.1s) due to:
- Automatic image optimization and format conversion
- Lazy loading with proper placeholders
- Responsive image serving
- Better compression algorithms
Reduced Unnecessary Network Requests
// Aggressive refetching - always stale
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: 0, // Always refetch
retry: false,
},
},
});
// Router config
defaultPreload: "intent",
defaultPreloadStaleTime: 0, // No caching
// Conservative caching strategy
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: 5 * 60 * 1000, // 5 minutes cache
retry: false,
},
},
});
Impact: Reduced server load and improved perceived performance by avoiding unnecessary refetches.
Better Resource Loading
// Intent-based preloading only
defaultPreload: "intent",
// Explicit prefetching on critical navigation paths
<Link href="/dashboard" prefetch={true}>
<Link href="/models" prefetch={true}>
<Link href="/changelog" prefetch={true}>
Impact: Better resource preloading for frequently accessed routes.
Improved Search Engine Optimization
This explains the dramatic SEO improvement (80 → 100)
// apps/ui/src/routes/__root.tsx
head: () => ({
meta: [
{ name: "title", content: "LLM Gateway" }, // ❌ WRONG!
{ property: "og:title", content: "LLM Gateway" },
],
}),
// apps/next/src/app/layout.tsx
export const metadata: Metadata = {
title: "LLM Gateway", // ✅ Generates proper <title> element
openGraph: {
title: "LLM Gateway",
},
};
The Problem: TanStack Start was generating invalid HTML:
<!-- What TanStack Start generated (INVALID) -->
<head>
<meta name="title" content="LLM Gateway"> <!-- ❌ Not a valid title! -->
<!-- Missing actual <title> element -->
</head>
<!-- What Next.js generates (VALID) -->
<head>
<title>LLM Gateway</title> <!-- ✅ Proper HTML title element -->
<meta property="og:title" content="LLM Gateway">
</head>
Impact: This single fix explains most of the SEO improvement because:
- Search engines prioritize the
<title>
element for ranking - Browser tabs display the
<title>
element content - Screen readers announce the
<title>
element for accessibility - Lighthouse SEO audits check for valid
<title>
elements
// Static generation for public pages
export async function generateStaticParams() {
// Pre-generate model pages, provider pages, changelog
}
// Dynamic rendering for authenticated pages
export const dynamic = "force-dynamic";
// Better metadata handling
export const metadata: Metadata = {
metadataBase: new URL("https://llmgateway.io"),
title: "LLM Gateway",
openGraph: { /* ... */ },
twitter: { /* ... */ },
};
Additional Impact: The SEO score improvement (80 → 100) also comes from:
- Automatic meta tag optimization
- Better SSR/hydration handling
- Proper Open Graph and Twitter Card generation
- Automatic sitemap generation
More Mature Ecosystem
- Uses Vinxi (newer, experimental build tool)
- Less mature optimization ecosystem
- Manual bundle optimization required
- Battle-tested Webpack/Turbopack bundling
- Automatic code splitting
- Tree shaking optimizations
- Better caching strategies
Enhanced Observability
// Client-side only PostHog
<PostHogProvider apiKey={config.posthogKey}>
// Both client and server-side analytics
import { PostHog } from "posthog-node";
// Server-side tracking
const posthogClient = new PostHog(config.posthogKey!, {
host: config.posthogHost,
flushAt: 1,
flushInterval: 0,
});
Impact: Better tracking and performance monitoring capabilities.
- Next.js built-in meta tag optimization
- Better SSR/hydration handling
- Automatic sitemap generation
- Superior crawlability
- Next.js automatic code splitting
- Mature bundler optimizations
- Better caching strategies
- Optimized runtime
- Better TypeScript integration
- More stable ecosystem
- Superior debugging tools
- Better deployment optimizations
The migration results demonstrate that this was not just a framework switch but a comprehensive optimization effort that included:
- ✅ Image optimization (biggest impact on Speed Index)
- ✅ Caching strategy improvements (React Query configs)
- ✅ Prefetching optimizations
- ✅ Static generation implementation
- ✅ Enhanced analytics setup
- ✅ Bundle optimization through mature tooling
TanStack Start is not inherently slower than Next.js. The performance gains came from:
- Next.js providing better built-in optimization primitives
- The development team implementing performance best practices during migration
- Access to a more mature ecosystem of optimization tools
The combination of framework capabilities + implementation best practices explains the dramatic performance improvements seen in the Lighthouse scores.
- Image optimization is critical for Speed Index performance
- Caching strategies matter for reducing unnecessary network requests
- Framework maturity provides better optimization primitives
- Migration is an opportunity to implement neglected optimizations
- Holistic approach yields better results than framework switching alone
This analysis was conducted by comparing the codebases in apps/ui
(TanStack Start) and apps/next
(Next.js) to understand the specific technical changes that contributed to the performance improvements.