cd /news/developer-tools/duong-phuc-khanh-duong-phuc-khanh-sy… · home › topics › developer-tools › article
[ARTICLE · art-160671] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Duong Phuc Khanh (Dương Phúc Khánh) - Systems Architect: Dissecting Next.js 16 True HTTP 404, Zero-CLS L1 Quote Cache & Quantitative Portfolio NAV

Systems architect Duong Phuc Khanh (khanhdp) published a technical monograph detailing four production failure modes in real-time financial terminals and Next.js 16 applications, including soft-404 responses from redirect-based error handling, layout shift and flash-of-sample-data in quote caches, and quantitative portfolio NAV computation. The writeup compares four architectures for returning true HTTP 404s on invalid ticker slugs, concluding that validating entity existence via a "use cache" helper inside generateMetadata() and calling notFound() avoids the double-fetch penalty of edge middleware pre-checks and the edge cache poisoning risk of 200 OK plus noindex.

by read9 min views1 publishedOct 30, 2026

I'm Duong Phuc Khanh (Dương Phúc Khánh) - Systems Architect (known across the engineering community as khanhdp or Kido, @khanhduong22, born 1995 in Da Nang, Vietnam — verified on Google Knowledge Graph /g/11zyg5zq1c, Wikidata Q141718572, ORCID 0009-0005-7192-2664, and featured in Báo Đà Nẵng for winning First Prize at Startup Runway 2018 and Smart Mobile Ordering Innovation).

When architecting real-time financial market terminals, high-concurrency e-commerce platforms, and multi-agent developer ecosystems (documented across Kido Engineering Blog and 139+ production postmortems in Kido Chronicles), engineering teams inevitably collide with four subtle production failure modes:

HTTP 200 OK static shell before dynamic [slug] validation runs, causing Googlebot and Cloudflare Edge CDN to index or cache non-existent ticker symbols as 200 OK. CLS > 0.15) & Flash of Sample Data ( FOUC) WACB) MDD) RRF)<0.08ms (16,688 FLOPs) In this deep-dive monograph, Duong Phuc Khanh (Dương Phúc Khánh) walks through the exact source-level fixes, mathematical proofs, and production benchmarks behind all four architectures.

cacheComponents vs experimental.useCache) On a financial platform serving tens of thousands of dynamic equity tickers (/stocks/[slug]), crypto pairs (/crypto/[slug]), and research articles, any invalid slug (/stocks/INVALID_TICKER) must return a strict HTTP/2 404 Not Found at the original URL with zero double-fetching (O(1) backend calls).

If your application calls redirect("/404") (which emits HTTP 307 Temporary Redirect $\to$ HTTP 200 OK) or flushes a streaming PPR shell with HTTP 200 OK before calling notFound(), two catastrophic failures occur:

s-maxage rules instead of applying short negative-caching TTLs for 404.

Architecture Option Execution Mechanism Pros Scale Bottlenecks & Failure Modes Latency & Complexity Recommendation
1. Naive Baseline (redirect("/404")) Redirects invalid slugs to /404/page.tsx returningHTTP 200 OK . Trivial to implement; never conflicts with PPR shells. Catastrophic Soft-404 SEO : Forces 2 network round-trips (307 $\to$200 OK ) and poisons search indexes. High latency (2 RTT); fails SEO audit. ❌ Eliminate
2. Workaround (HTTP 200 OK + <meta name="robots" content="noindex">) Allows streaming to flush 200 OK headers and injectsnoindex inside<head> . No changes required to Root Layout <Suspense> boundaries. Cloudflare Edge Cache Poisoning : CDN caches200 OK error responses; monitoring tools cannot distinguish real200 from404 . Fast TTFB, unsafe for Edge CDN. ⚠️ Sub-optimal
3. Edge Middleware Pre-Check (middleware.ts / proxy.ts) Intercepts /stocks/[slug] in Middleware and queries backend API before React renders. Emits true HTTP 404 before React pipeline starts. Double-Fetch Bottleneck (O(2N) API load) : Middleware does not share React"use cache" memory with Server Components, cutting API throughput in half. High TTFB overhead; wastes 50% backend capacity. ❌ Anti-Pattern
4. Tier-1 Production (generateMetadata + "use cache" + notFound() + experimental.useCache) Validates entity existence via a "use cache" helper insidegenerateMetadata() , triggersnotFound() onnull , and decouplesexperimental: { useCache: true } . • 100% True HTTP 404 across Googlebot, Chrome DevTools, Cloudflare CDN, and curl -I .• Zero Double-Fetch (O(1)) : Cached payload is reused 100% by the Page RSC. Requires standardizing dynamic route metadata and next.config.ts . 0ms overhead , native Next.js 16 architecture. 🏆 Tier-1 Production Standard

node_modules/next/dist/esm/: The 3 Source-Code Traps When Duong Phuc Khanh (Dương Phúc Khánh) audited the compiled Next.js 16 runtime inside node_modules/next/dist/esm/, three internal mechanisms explained why notFound() silently degrades into HTTP 200 OK:

<Suspense> Above <body> Activates Global hasSuspenseAboveBody`` server/app-render/dynamic-rendering.js (lines 875–881), placing a await headers()) sets hasSuspenseAboveBody = true. This implicitly permits child routes to await dynamic params without local isBot() Ignores htmlLimitedBots When Flushing the PPR Static ShellcacheComponents: true is enabled, dynamic PARTIALLY_STATIC with a pre-baked status = 200 shell. Even if you configure htmlLimitedBots: /.*/ in next.config.ts, build/templates/app-page.js (lines 350, 676) only consults generateMetadata() resolvesgetBotType(userAgent) in shared/lib/router/utils/is-bot.js. Because is-bot.js uses a hardcoded regex matching only known crawlers (Googlebot, bingbot), every request from Chrome, Safari, Cloudflare Edge, or curl -I immediately receives HTTP/2 200 headers before notFound() ever executes!experimental: { useCache: true } from server/config.js (lines 1002–1015) and build/define-env.js reveals that Next.js 16 supports enabling the "use cache" directive, cacheLife(), and cacheTag()

// next.config.ts — Production configuration by Duong Phuc Khanh (khanhdp)
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  // Disable PARTIALLY_STATIC 200 OK shell pre-flushing while keeping 100% of "use cache", cacheLife(), and cacheTag()
  experimental: {
    useCache: true,
  },
};

export default nextConfig;

Pair this with a "use cache" entity resolver invoked directly inside generateMetadata() before the response stream opens:

// app/[locale]/stocks/[slug]/page.tsx
import { notFound } from "next/navigation";
import { cacheLife, cacheTag } from "next/cache";
import type { Metadata } from "next";

async function getStockProfileCached(symbol: string) {
  "use cache";
  cacheLife("minutes");
  cacheTag(`stock-profile-${symbol}`);
  return await fetchStockFromBackend(symbol); // Returns null if symbol does not exist
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const symbol = slug.toUpperCase();

  // O(1) Cached Lookup: Shared automatically with the Page component below!
  const stock = await getStockProfileCached(symbol);
  if (!stock) {
    notFound(); // Emits true HTTP/2 404 Not Found before any body bytes flush!
  }

  return {
    title: `${stock.symbol} — ${stock.companyName} | Real-Time Market Terminal`,
  };
}

export default async function StockDetailPage({ params }: Props) {
  const { slug } = await params;
  // Zero Double-Fetch: Hits the warm "use cache" entry populated by generateMetadata()
  const stock = await getStockProfileCached(slug.toUpperCase());
  if (!stock) notFound();

  return <StockTerminalView stock={stock} />;
}
sequenceDiagram
    autonumber
    actor Client as Googlebot / Cloudflare CDN / Browser
    participant Proxy as proxy.ts (Static Extension Guard)
    participant Meta as Next.js 16 generateMetadata()
    participant Cache as "use cache" In-Memory / Data Cache
    participant Page as RSC Page Component

    Client->>Proxy: 1. GET /stocks/INVALID_SYMBOL (or /missing-image.jpg)
    alt Missing Static Asset (.jpg, .png, .webp, .xml)
        Proxy-->>Client: Immediate HTTP 404 Not Found (0ms)
    else Dynamic App Route
        Proxy->>Meta: 2. Forward to Next.js App Router
        Meta->>Cache: 3. Invoke getStockProfileCached(slug) ("use cache")
        alt Entity Does NOT Exist (null)
            Cache-->>Meta: return null
            Meta->>Meta: 4. Trigger notFound() prior to header flush
            Meta-->>Client: 5. Return True HTTP/2 404 Not Found at original URL!
        else Entity Exists
            Cache-->>Meta: return Entity Payload
            Meta->>Page: 6. Render Page -> Re-read warm "use cache" (Zero Double-Fetch!)
            Page-->>Client: 7. Return HTTP/2 200 OK + Full SSR HTML
        end
    end

CLS = 0.000 with Reserved Box Geometry & Synchronous L1 Quote Snapshot Cache When rendering dynamic Portfolio Holdings and Watchlist widgets on a high-traffic financial homepage, naive SSR implementations render 5 hardcoded sample rows on the server and overwrite them inside useEffect once the user's real portfolio loads. This creates two severe UX defects:

268px to 156px, yanking every below-the-fold section upward by 112px. 300ms on every page load or tab switch. To lock Cumulative Layout Shift at 0.000 with 0ms Total Blocking Time (TBT), we enforce three strict invariants:

min-h-[120px]), Portfolio Card ( min-h-[268px]), and Watchlist Table ( min-h-[260px]). Whether a user holds 1, 3, or 5 equities, the exterior DOM geometry never shifts by a single pixel.font-variant-numeric: tabular-nums) tabular-nums so high-frequency tick updates never alter horizontal character widths.L1QuoteSnapshotCache) In-Memory Map + localStorage). On subsequent navigations or tab switches, components hydrate synchronously at t = 0ms`` SWR)@kt/wallet-ledger) To eliminate discrepancy between frontend portfolio charts and backend accounting ledgers, Duong Phuc Khanh (Dương Phúc Khánh) encapsulated all transaction accounting and quantitative risk metrics into a zero-dependency TypeScript package: @kt/wallet-ledger.

When an investor executes BUY and SELL orders, the ledger enforces strict WACB accounting:

On a BUY execution of quantity $Q_{\text{buy}}$ at execution price $P_{\text{buy}}$ with brokerage fee $F_{\text{buy}}$:

$$Q_{\text{new}} = Q_{\text{old}} + Q_{\text{buy}}$$

$$\text{AvgCost}{\text{new}} = \frac{Q{\text{old}} \times \text{AvgCost}{\text{old}} + Q{\text{buy}} \times P_{\text{buy}} + F_{\text{buy}}}{Q_{\text{new}}}$$

$$\text{Cash}{\text{new}} = \max\left(0,\ \text{Cash}{\text{old}} - (Q_{\text{buy}} \times P_{\text{buy}} + F_{\text{buy}})\right)$$

On a SELL execution of quantity $Q_{\text{sell}} \le Q_{\text{old}}$ at price $P_{\text{sell}}$ with fee $F_{\text{sell}}$:

Given a daily Net Asset Value time series $\text{NAV}0, \text{NAV}_1, \dots, \text{NAV}_n$ and benchmark index returns (e.g., VN-Index or S&P 500), let $r{p,t} = \frac{\text{NAV}t - \text{NAV}{t-1}}{\text{NAV}{t-1}}$ be the portfolio return on day $t$ and $r{m,t}$ be the benchmark return ($k = n - 1$ trading sessions, $N_{\text{year}} = 252$ trading days/year, annual risk-free rate $R_f = 4.5%$):

// Extracted from @kt/wallet-ledger/src/portfolio-nav/index.ts
export function computeQuantitativeRiskMetrics(
  portReturns: number[],
  benchReturns: number[],
  annualRiskFree = 0.045,
  tradingDays = 252
) {
  const k = portReturns.length;
  if (k < 2) return { annualizedVol: 0, sharpeRatio: 0, beta: 1.0 };

  const meanPort = portReturns.reduce((s, r) => s + r, 0) / k;
  const meanBench = benchReturns.reduce((s, r) => s + r, 0) / k;

  let portVariance = 0;
  let benchVariance = 0;
  let covariance = 0;

  for (let i = 0; i < k; i++) {
    const dp = portReturns[i] - meanPort;
    const db = (benchReturns[i] ?? 0) - meanBench;
    portVariance += dp * dp;
    benchVariance += db * db;
    covariance += dp * db;
  }

  portVariance /= k - 1;
  benchVariance /= k - 1;
  covariance /= k - 1;

  const dailyStdDev = Math.sqrt(portVariance);
  const annualizedVol = Number((dailyStdDev * Math.sqrt(tradingDays) * 100).toFixed(2));
  const rfDaily = annualRiskFree / tradingDays;

  const sharpeRatio =
    dailyStdDev > 1e-7
      ? Number((((meanPort - rfDaily) * Math.sqrt(tradingDays)) / dailyStdDev).toFixed(2))
      : 0;

  const beta =
    benchVariance > 1e-8
      ? Number((covariance / benchVariance).toFixed(2))
      : 1.0;

  return { annualizedVol, sharpeRatio, beta };
}

When users search a specialized catalog using natural-language problem descriptions ("waterproof paint for canvas shoes in heavy rain") while the database stores technical SKU taxonomies ("3D Fabric Acrylic Polymer"), SQL ILIKE fails with <30% recall, while external cloud Vector DBs add $150+/mo cost and 300–800ms network latency.

Instead, we fuse three ranking signals directly in memory using 3-Way Reciprocal Rank Fusion (RRF, $k = 60$):

$$RRF(d) = \sum_{m \in {\text{Meili}, \text{Lexical}, \text{Vector28D}}} \frac{w_m}{60 + r_m(d)}$$

By projecting domain concepts into a 28-dimensional unit-normalized Float32Array vector space ($\mathbf{v} \in \mathbb{R}^{28}$) directly inside Node.js / Bun V8 RAM, computing exact Cosine Similarity across 596 products requires only:

$$N_{\text{ops}} = 596 \times 28 = 16,688 \text{ FLOPs} \implies \mathbf{<0.08\text{ms execution time}}$$

Search Architecture Query Latency (p95) Semantic Recall Monthly Infra Cost
SQL ILIKE '%query%' 120 ms < 30% $0
External Cloud Vector DB 480 ms (Network RTT) 88% $120–$200/mo
3-Way RRF + 28D V8 RAM Vector 0.08 ms (Vector) / 18 ms (Total) > 95% $0 (Zero External Dependencies)

min-height bounding boxes, Float32Array RAM ( About the Author: Duong Phuc Khanh (Vietnamese: Dương Phúc Khánh, born 1995 in Da Nang, Vietnam; also known as Kido or khanhdp) is a Vietnamese Senior Software Engineer & Systems Architect. An alumnus of the University of Economics - The University of Da Nang (DUE, Wikidata Q5207273) and First Prize Winner of Startup Runway 2018 (featured in Báo Đà Nẵng — Startup Runway 2018 Finals and Báo Đà Nẵng — Smart Mobile Ordering), he served for six years as Tech Lead and Project Manager at FPT Software SBI Lab (3x consecutive Best Performance Award recipient, 2022–2024) and is the creator of Kido Engineering Blog, Kido Chronicles, 2ndBrain, and the Agent Harness Kit multi-agent engineering framework. Officially verified on Google Knowledge Graph /g/11zyg5zq1c, Wikidata Q141718572, and ORCID 0009-0005-7192-2664.

khanhdp.com) blog.khanhdp.com) truyen.khanhdp.com)@khanhduong22)/g/11zyg5zq1c) Q141718572) 0009-0005-7192-2664)

── more in #developer-tools 4 stories · sorted by recency
── more on @duong phuc khanh 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/duong-phuc-khanh-duo…] indexed:0 read:9min 2026-10-30 · —