惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - Franky
有赞技术团队
有赞技术团队
博客园 - 司徒正美
量子位
N
News and Events Feed by Topic
T
Threatpost
Last Week in AI
Last Week in AI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
酷 壳 – CoolShell
酷 壳 – CoolShell
C
CERT Recently Published Vulnerability Notes
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
I
Intezer
人人都是产品经理
人人都是产品经理
T
Tenable Blog
IT之家
IT之家
雷峰网
雷峰网
腾讯CDC
博客园 - 聂微东
V
Visual Studio Blog
S
SegmentFault 最新的问题
Scott Helme
Scott Helme
Spread Privacy
Spread Privacy
月光博客
月光博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
大猫的无限游戏
大猫的无限游戏
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
T
Tailwind CSS Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
罗磊的独立博客
N
Netflix TechBlog - Medium
J
Java Code Geeks
宝玉的分享
宝玉的分享
F
Full Disclosure
WordPress大学
WordPress大学
A
Arctic Wolf
小众软件
小众软件
AWS News Blog
AWS News Blog
Attack and Defense Labs
Attack and Defense Labs
NISL@THU
NISL@THU
AI
AI
Hugging Face - Blog
Hugging Face - Blog
F
Fortinet All Blogs
云风的 BLOG
云风的 BLOG
N
News | PayPal Newsroom
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Your Page Is Only as Fast as Your Slowest API: The Case for Streaming SSR
reactuse.com · 2026-04-27 · via DEV Community

reactuse.com

You open Chrome DevTools on your product page. The Network tab has exactly one row that matters — the HTML document — and it's been stuck on "Waiting for response" for 1,400ms. From the browser's point of view, the server is a black box that hasn't said a word yet.

Pull up the server's APM trace and the black box opens up. Inside that 1.4 seconds, your loader fanned out to five upstream services:

  • Auth check — 30ms
  • Cart count — 50ms
  • Header navigation data — 80ms
  • Product details — 200ms
  • Personalized recommendations — 1,400ms

Time to first byte: 1,400ms. Time to first paint: 1,420ms. Every user, every page view, waits for the slowest API before the browser sees a single byte of HTML — even though four-fifths of the page's data was ready in 200ms.

This is the short-board effect, and almost every traditional SSR app pays it. The bucket fills to the height of the shortest plank. The page renders at the speed of the slowest dependency. Streaming SSR is the framework-level fix, and in 2026 it's no longer optional. Here's why.

The math: why this gets worse, not better

The short-board effect compounds with the number of data dependencies. Suppose each API in your loader has a 1% chance of running 5x slower than its median (cold cache, GC pause, network hiccup, dependency of a dependency timing out). With one API, your p99 page latency is fine. With five APIs, the probability that at least one is slow on a given request is:

1 - (1 - 0.01)^5 = 4.9%

Enter fullscreen mode Exit fullscreen mode

With ten APIs it's 9.6%. The page p99 isn't the average of API p99 — it's the max. Adding a fast endpoint to your loader can't make the page faster, but adding a slow one always makes the page slower.

This is the architectural reality of every modern web app:

  • Pages depend on user data, content data, navigation data, A/B test buckets, feature flags, recommendations, ads, analytics consent, session state.
  • Each one is a separate service, often a separate team, often a separate company.
  • Slow dependencies rarely get removed. They just get added to.
  • Personalized data can't be cached.
  • Real-time data can't be cached.
  • The CDN can't help — the response is per-user.

If your page has any external dependency you don't fully control, you have a slowest-API problem. The question is what you do about it.

What traditional SSR does

The standard SSR loader pattern is Promise.all:

export async function loader() {
  const [user, nav, product, recs] = await Promise.all([
    getUser(),         // 30ms
    getNav(),          // 80ms
    getProduct(),      // 200ms
    getRecs(),         // 1400ms ← waits for this
  ])
  return { user, nav, product, recs }
}

Enter fullscreen mode Exit fullscreen mode

The render function gets called once, with all data resolved. The HTML is generated in one pass. The response body starts flowing only after the slowest fetch returns.

From the user's perspective:

  • TTFB: 1,400ms (waiting for recs)
  • First paint: ~1,420ms (TTFB + parse)
  • Largest contentful paint: ~1,450ms

The user is staring at a blank tab for 1.4 seconds. The browser hasn't even started parsing HTML, because the server hasn't sent any. Adding a CDN doesn't help — the document itself is slow. Adding more bandwidth doesn't help — the bottleneck is server-side compute waiting on I/O.

Why "just fetch on the client" isn't the answer

The first instinct for many teams is: render the shell server-side, fetch the slow data after hydration. This trades one problem for two.

Waterfall: HTML → CSS → JS → hydrate → fetch → render. Each step blocks the next. On a slow network, the time-to-interactive for the slow section is even higher than it was server-side, because the client now has to download and parse JS first.

SEO: Search crawlers read what's in the HTML. Recommendations rendered after hydration aren't in the initial document. For pages where the slow data is the important data — product reviews, comparison tables, location-specific content — client fetching makes the page invisible to ranking signals.

Client-side fetching is sometimes the right call (deeply interactive widgets, viewport-triggered loads), but it's not a fix for the slowest-API problem. It just hides the latency behind a loading spinner that the user has to wait through anyway.

Why caching alone isn't the answer

"Cache it" is the second instinct. Caching helps, and you should do it. But it doesn't solve the architectural problem:

  • Cold caches happen. Every deploy. Every cache eviction. Every key miss. The first user after a flush still pays full latency.
  • Personalized data can't be cached. Or can be cached only per-user, with hit rates that hover near zero on long-tail traffic.
  • Real-time data can't be cached. Inventory, prices, live scores, chat presence — caching them is the bug.
  • Stale data is its own bug. Aggressive caching ships wrong information. Conservative caching loses most of the benefit.

Streaming SSR composes with caching. You cache what you can, you stream what you can't. The two strategies aren't alternatives — they target different problems.

What streaming SSR actually does

Streaming SSR breaks the all-or-nothing render. The server sends two kinds of data in one response:

  1. The shell + fast data — flushed to the client immediately, while slow fetches are still in flight.
  2. The slow data — streamed in as Suspense fallbacks resolve, written to the same response body in chunks.

The browser starts parsing HTML as soon as the first chunk arrives. CSS and fonts kick off in parallel with the rest of the document. By the time the slow API returns, the user has already been reading the page for a second.

// app/product/loader.ts
import { defer } from '@paretojs/core'

export async function loader(ctx) {
  const [user, nav, product] = await Promise.all([
    getUser(ctx),     // 30ms
    getNav(ctx),      // 80ms
    getProduct(ctx),  // 200ms
  ])

  return defer({
    user, nav, product,           // resolved — included in shell
    recs: getRecs(ctx),           // promise — streams when ready
  })
}

Enter fullscreen mode Exit fullscreen mode

// app/product/page.tsx
import { Await } from '@paretojs/core'
import { Suspense } from 'react'

export default function ProductPage({ data }) {
  return (
    <>
      <Header user={data.user} nav={data.nav} />
      <ProductDetail product={data.product} />

      <Suspense fallback={<RecsSkeleton />}>
        <Await resolve={data.recs}>
          {(recs) => <Recommendations items={recs} />}
        </Await>
      </Suspense>
    </>
  )
}

Enter fullscreen mode Exit fullscreen mode

From the user's perspective now (assuming ~40ms RTT to the nearest edge):

  • TTFB: ~240ms — RTT plus the slowest fetch you still await in the loader (product, 200ms). The shell flushes the moment that resolves; recs is still in flight.
  • First paint: ~290ms after the document parses
  • Header, nav, product: visible at ~290ms
  • Recommendations: stream in around 1,440ms — but the user has been reading the page for over a second already

Same total work. Same backend latency. Completely different user experience. The slow API no longer holds the rest of the page hostage.

A concrete numbers table

Same product page, same APIs, two render strategies:

Metric Traditional SSR Streaming SSR
TTFB ~1,440ms (RTT + slowest fetch) ~240ms (RTT + slowest awaited fetch)
First paint ~1,460ms ~290ms
Above-the-fold visible ~1,460ms ~290ms
Recommendations visible ~1,460ms ~1,440ms
Bytes to first byte full page shell only
SEO full page full page (slow data still in HTML, just later in the stream)
Backend cost identical identical

Streaming SSR doesn't fix layout shift — that's on you. A <Suspense> fallback whose rendered height doesn't match the real content jumps just as hard as a client-side skeleton would. Budget the height (fixed min-height, aspect-ratio boxes, skeleton lines that match line counts) and CLS stays clean.

That last column is the important one. Streaming SSR doesn't make the slow API faster. It just stops it from blocking the rest of the page. The fix is structural, not performance-magic.

Why this is the new default

In 2024, streaming SSR was a power-user feature. In 2026, it's table stakes. Every serious React framework supports it: Next.js (via RSC + Suspense), Remix and React Router (via defer() and <Await>), TanStack Start, Pareto. The reason isn't fashion — it's that the alternative is shipping a page that's measurably worse for everyone whose backend has more than one dependency. Which is everyone.

The remaining decision isn't "should I use streaming SSR." It's "which abstraction do I want to use to do it." React Server Components are one answer — they bring streaming, but they also bring "use client", the dual-component model, and a lot of mental overhead about which code runs where. The other answer is the loader-plus-defer() model: ordinary React components, ordinary Suspense boundaries, one extra primitive in your loader. Pareto picks the second path. The result is roughly 1/4 the client JS of a Next.js equivalent and none of the RSC mental tax — but the streaming behavior is the same, because that's what the response actually does on the wire.

What to actually do

If you have an existing SSR app, the migration is mechanical and incremental:

  1. Find your slow APIs. Look at p95 of every fetch in your loaders. Anything past 200ms is a candidate.
  2. Move the slow ones into defer(). Don't await them in the loader. Pass the promise.
  3. Wrap their consumer in <Suspense> + <Await>. Provide a fallback that reserves the right amount of space.
  4. Measure TTFB and FCP. They should drop to roughly your RTT plus the slowest fetch you still await in the loader — not the slowest fetch overall.

You're not rewriting the page. You're cutting the wire that ties the fast paint to the slow data.

npx create-pareto@latest my-app
cd my-app && npm install && npm run dev

Enter fullscreen mode Exit fullscreen mode

Related reading on paretojs.tech: