Improving TTFB for SSR Apps: Caching, Databases, and Edge Strategy
TTFB Is the Ceiling of Your SSR Performance
Server-side rendered applications generate HTML on every request. That is their strength — fresh, dynamic content delivered to the browser and search engines without relying on client-side JavaScript execution. But it is also their weakness. Every millisecond of server processing time adds directly to Time to First Byte (TTFB), and TTFB is the foundation on which every other performance metric rests. A page cannot start rendering until the browser receives the first byte of HTML.
For SSR applications built with frameworks like Next.js, Nuxt, or SvelteKit, improving TTFB requires a systematic approach: reduce the work the server does per request through caching, optimize the database queries that generate page data, and leverage edge infrastructure to serve responses closer to users.
Understanding What Makes SSR Slow
Break down the time your server spends generating a response. A typical SSR request involves:
- Routing: Matching the request URL to a page handler (usually fast — under 1ms).
- Data fetching: Querying databases, calling APIs, and retrieving the data needed to render the page. This is usually the biggest contributor to TTFB.
- Rendering: Running the component tree with the fetched data to produce HTML. For complex pages with many components, this can take 50-200ms.
- Serialization: Encoding the rendered HTML and any client-side hydration data for transmission.
Data fetching dominates in most applications. If a single page requires five database queries averaging 50ms each, that alone is 250ms before rendering even starts. The optimization strategy is to reduce the number, duration, and frequency of data fetches.
Caching Strategies for SSR
Full-Page Caching
If a page is the same for all visitors and does not change frequently, cache the entire HTML response. Subsequent requests serve the cached HTML in microseconds instead of re-rendering. Use a reverse proxy (Nginx, Varnish) or a CDN to cache at the edge.
Set appropriate cache-control headers. For pages that update periodically, use stale-while-revalidate — the cache serves the stale version immediately while fetching a fresh one in the background. This provides instant TTFB while keeping content reasonably fresh.
Fragment Caching
When a page contains a mix of static and dynamic content, cache the static fragments individually. The navigation menu, footer, sidebar widgets, and product category lists rarely change — cache them and assemble the page from cached fragments plus the dynamic content. This reduces rendering time without sacrificing personalization where it matters.
Data-Level Caching
Cache the results of expensive database queries and API calls in Redis or Memcached. The first request computes the result, subsequent requests read from cache. Use cache keys that reflect the query parameters, and set TTLs that match the data's freshness requirements.
For SSR specifically, data-level caching is often the most effective approach because it reduces data fetching time (the main bottleneck) while allowing the rendering step to proceed normally with fresh template logic.
Incremental Static Regeneration (ISR)
If you use Next.js, ISR combines the benefits of static generation and server-side rendering. Pages are pre-generated at build time and served statically, but they are regenerated in the background at a configurable interval. This gives you SSG-level TTFB with SSR-level content freshness. ISR is particularly effective for pages like blog posts, product listings, and marketing pages that update periodically but do not need real-time data.
Database Optimization for SSR
Every SSR request that hits the database contributes to TTFB. Optimize aggressively:
Reduce Query Count
Audit the queries each page triggers. SSR applications often suffer from the N+1 query problem — fetching a list of items and then querying for each item's related data individually. Use eager loading, JOINs, or batch queries to reduce the number of database round trips.
Optimize Individual Queries
Use EXPLAIN ANALYZE to identify slow queries. Add indexes for columns used in WHERE clauses and JOINs. Avoid SELECT * — fetch only the columns the page needs. For pagination queries, use cursor-based pagination instead of OFFSET, which becomes slower as the offset increases.
Connection Pooling
SSR applications can exhaust database connections quickly because each request may need a connection during rendering. Use a connection pooler (pgBouncer for PostgreSQL, connection pool middleware for MySQL) to multiplex application connections onto a smaller pool of database connections. This prevents connection exhaustion and reduces the overhead of establishing new connections.
Edge Strategy
CDN with Edge Caching
Place a CDN in front of your SSR application. The CDN caches responses at edge locations worldwide. Even with short TTLs (60 seconds), the CDN absorbs the majority of traffic during surges and reduces TTFB for visitors far from your origin server.
Edge Rendering
Some platforms support running SSR at the CDN edge — executing your server-side code on edge servers distributed globally rather than on a single origin. This eliminates the latency penalty of long-distance origin requests. The trade-off is that edge environments have limited compute resources and may not support all database connection patterns. Evaluate whether your application's data fetching can work within edge constraints.
Regional Deployments
If edge rendering is not feasible, consider deploying your SSR application in multiple regions with a load balancer that routes users to the nearest region. This reduces the geographic distance between the server and the user, directly improving TTFB. The complexity lies in database replication and consistency — see the multi-region guide for details.
Rendering Optimization
While data fetching is usually the dominant factor, rendering time can be significant for complex pages:
- Simplify the component tree: Deeply nested component trees with many conditional branches take longer to render. Flatten where possible and remove unused components.
- Avoid expensive computations during render: Data transformations, sorting, and filtering should happen during data fetching or in a cached computation, not during the rendering phase.
- Stream the response: Modern SSR frameworks support streaming — sending the HTML to the browser as it is rendered rather than waiting for the entire page to complete. This improves perceived TTFB because the browser can start parsing and rendering while the server continues generating the rest of the page.
Monitoring TTFB in Production
Lab measurements (running Lighthouse on your development machine) do not reflect real-world TTFB. Implement Real User Monitoring (RUM) to measure TTFB as experienced by actual visitors on diverse networks and devices. Track TTFB percentiles (p50, p75, p95) over time to identify trends and regressions.
Set up alerts for TTFB regressions. A sudden increase in p95 TTFB after a deployment often indicates a new slow query, a missing cache, or an added data dependency. Catching these regressions early prevents them from affecting search rankings and user experience for extended periods.
A Practical Checklist
- Profile your SSR pages to identify the biggest TTFB contributors (data fetching, rendering, serialization).
- Implement data-level caching with Redis for expensive queries and API calls.
- Enable full-page caching (CDN or reverse proxy) for public, non-personalized pages.
- Optimize database queries: add indexes, reduce query count, use connection pooling.
- Consider ISR or stale-while-revalidate for pages that do not need real-time data.
- Place a CDN in front of your application for edge caching and geographic proximity.
- Monitor TTFB with RUM and alert on regressions.
The Bottom Line
SSR performance is ultimately about how fast your server can generate HTML. Every optimization that reduces data fetching time, caches computed results, or brings the server closer to the user directly improves TTFB. Approach it systematically: measure, identify the bottleneck, fix it, verify the improvement, and repeat. The payoff is better Core Web Vitals, higher search rankings, and a snappier experience for every visitor.