Initial Diagnosis: The Hidden Bottlenecks Behind a 66 Score

For most web engineers, performance optimization advice is often reduced to cliché bullet points like 'compress images' or 'minify your code'. However, testing a modern React or TanStack Start application under mobile emulation (throttled 4G / simulated CPU slowdown) with Google Lighthouse frequently reveals a disappointing score in the 60s, with Largest Contentful Paint (LCP) climbing beyond 7 seconds.

Capturing a granular Performance trace in Chrome DevTools revealed that raw image size was merely a symptom. The real culprits were three structural architectural bottlenecks: 1) A third-party script blocking the critical rendering path for 180 ms, 2) All blog post markdown bodies (137 KB) being accidentally bundled into the initial client bundle, and 3) Third-party analytics executing eagerly on page boot.

Bash
# Lighthouse Mobil Lantern Simülasyonu (Önceki Durum):
# Performans Skoru: 66 / 100
# FCP (First Contentful Paint): 3.1 s
# LCP (Largest Contentful Paint): 7.7 s
# TBT (Total Blocking Time): 520 ms
# Render-blocking kaynaklar: 180 ms gecikme

The Invisible Render-Blocking Trap: Cloudflare's email-decode.min.js

Deep in server-rendered HTML payloads lurks an invisible performance killer: Cloudflare's 'Email Address Obfuscation' feature. When your server renders a literal 'mailto:user [at] domain.com' pattern, Cloudflare's edge proxy rewrites the link and injects a synchronous external script tag pointing to 'email-decode.min.js' at the bottom of the body.

On mobile 4G networks, fetching and executing this script adds a 170–180 ms render-blocking delay. The solution is straightforward: never render raw 'mailto:' attributes in server-side markup. By assembling the email address client-side upon explicit user action (click/copy) rather than in the raw SSR string, Cloudflare never triggers the rewrite, eliminating the render-blocking script entirely.

TypeScript
// ÖNCE (ZARARLI): Düz e-posta href'i Cloudflare email-decode.min.js enjekte eder
<a href={"mailto:kullanici" + " [at] " + "alanadi.com"}>İletişim</a>

// SONRA (SIFIR BLOKAJ): Dinamik tetikleme ve bot-proof montaj
const EMAIL = ["kullanici", "alanadi.com"].join(String.fromCharCode(64));
<a href="/contact" onClick={(e) => { e.preventDefault(); window.location.href = `mailto:${EMAIL}`; }}>
  {EMAIL}
</a>

Decoupling Heavy Content: Virtual Module Code-Splitting

A common architecture pitfall in modern full-stack frameworks is importing complete content payloads into global route loaders. When a homepage loader references a monolithic content array, the bundler statically inlines all markdown/JSON article bodies directly into the shared client entry chunk.

Visitors loading the homepage were forced to download hundreds of kilobytes of unread articles. We resolved this by engineering a custom Vite virtual module plugin (cmsContentPlugin). The entry chunk only includes a lightweight 'postIndex' metadata catalog (slugs, titles, reading minutes), while full article bodies are strictly deferred to route-level dynamic imports ('await import(...)').

TypeScript
// scripts/cms-content-plugin.ts (Özet Mantık)
export function cmsContentPlugin(): Plugin {
  return {
    name: "published-cms-content",
    resolveId(id) {
      if (id === "virtual:site-posts") return "\0virtual:site-posts";
      if (id === "virtual:site-index") return "\0virtual:site-index";
    },
    load(id) {
      if (id === "\0virtual:site-index") {
        // Sadece hafif kart metadatasını dışa aktar (5 KB)
        return `export const postIndex = ${JSON.stringify(lightweightMetadata)};`;
      }
      if (id === "\0virtual:site-posts") {
        // Tam makale gövdeleri sadece talep edildiğinde ayrı chunk'a ayrılır (137 KB)
        return `export const posts = ${JSON.stringify(fullArticles)};`;
      }
    }
  };
}

Zeroing Analytics Overhead: Interaction-Deferred Loading & Reverse Proxy

Third-party telemetry SDKs are notorious for inflating Total Blocking Time (TBT). Executing heavy analytics suites on the initial load event hogs the main thread during hydration, turning a potentially snappy page into an unresponsive frozen viewport on low-end devices.

We tackled this by implementing an interaction-deferred pattern. Rather than initializing PostHog eagerly on DOMContentLoaded, we attach passive one-time listeners to user gestures ('pointerdown', 'scroll', 'touchstart'). The SDK is fetched asynchronously during browser idle time, reducing initial analytical TBT to exactly 0 ms.

To combat tracking drop-offs caused by ad blockers (uBlock Origin, Brave Shields), we configured a server-side reverse proxy on Vercel (/ingest rewrites). Traffic flows through 'umutgungorr.com/ingest', qualifying as trusted first-party telemetry while eliminating data loss.

TypeScript
// Vercel Ters Proxy (vercel.json):
{
  "rewrites": [
    {
      "source": "/ingest/static/:path*",
      "destination": "https://eu-assets.i.posthog.com/static/:path*"
    },
    {
      "source": "/ingest/:path*",
      "destination": "https://eu.i.posthog.com/:path*"
    }
  ]
}

// İlk Etkileşimde Asenkron Başlatma (src/lib/posthog.ts):
const INTERACTION_EVENTS = ["pointerdown", "touchstart", "scroll", "click"];
function initOnFirstInteraction() {
  const onInteract = () => {
    INTERACTION_EVENTS.forEach(e => window.removeEventListener(e, onInteract));
    requestIdleCallback(() => void import("posthog-js").then(mod => mod.default.init(...)));
  };
  INTERACTION_EVENTS.forEach(e => window.addEventListener(e, onInteract, { once: true, passive: true }));
}

Eliminating Navigation Stutter: Synchronous Transitions with resolvedLocation

One of the most noticeable UX glitches in client-side routing happens when a user clicks a navigation link: the header pill instantly slides to the target tab, while the main viewport lags for several hundred milliseconds before abruptly popping in. This creates an awkward, unpolished stutter.

This occurs because router 'location.pathname' updates immediately on click, whereas '<Outlet />' remains on the old page until the incoming route chunk resolves. By binding header transition states to 'resolvedLocation' and enabling instant intent preloading, incoming routes mount synchronously with zero perceptible lag.

TypeScript
// SiteShell.tsx - Menü göstergesini ve içeriği aynı anda senkronize etme:
const pathname = useRouterState({
  select: (s) => (s.resolvedLocation ?? s.location).pathname,
});

// router.tsx - Intent önbellekleme:
export function createRouter() {
  return createTanStackRouter({
    routeTree,
    defaultPreload: "intent",
    defaultPreloadDelay: 20,
    defaultPreloadStaleTime: 60_000,
  });
}

Results: 100/100 Desktop & 98 Mobile Real-World Metrics

Running rigorous Lighthouse audits in the live production environment following these targeted architectural refinements yielded the following verified metrics:

Desktop Lighthouse achieved a flawless 100 / 100 (FCP: 0.5s, LCP: 0.6s, TBT: 0ms, CLS: 0). Mobile performance jumped from 66 to 98 / 100, slashing LCP from 7.7s to 2.1s and TBT to near zero—all accomplished without removing a single visual element, glassmorphic card, or motion animation.

Text
# Optimizasyon Öncesi vs Sonrası Karşılaştırma Tablosu:
# ------------------------------------------------------------
# Metrik                | Önceki Değer     | Optimizasyon Sonrası
# ------------------------------------------------------------
# Mobil Lighthouse      | 66 / 100         | 98 / 100 (Yeşil)
# Masaüstü Lighthouse   | 84 / 100         | 100 / 100 (Kusursuz)
# Mobil LCP             | 7.7 s            | 2.1 s (↓ %72 Hızlı)
# Mobil FCP             | 3.1 s            | 1.8 s (↓ %42 Hızlı)
# Total Blocking Time   | 520 ms           | 0 - 50 ms
# Render-Blocking JS    | 180 ms           | 0 ms (Kaldırıldı)
# Ad Blocker Veri Kaybı | %25 - %30        | %0 (Ters Proxy ile)

Source: Google Chrome Developers: Optimize Largest Contentful Paint

Source: PostHog Docs: Reverse Proxy on Vercel

Source: TanStack Router: Preloading and Router State Optimization

Source: Open-Source Projects & Systems Case Studies