` inside a `
` tag), which the browser's parser auto-corrects, causing a mismatch with React's virtual DOM.
**Solutions:**
+
1. **Defer rendering to mount** using `useEffect` or `useSyncExternalStore`:
```tsx
const [isMounted, setIsMounted] = useState(false);
@@ -78,7 +89,9 @@ A hydration mismatch occurs when the initial pre-rendered HTML sent from the ser
2. **Disable SSR for specific dynamic components** using Next.js dynamic imports:
```tsx
import dynamic from 'next/dynamic';
- const DynamicComponent = dynamic(() => import('./Component'), { ssr: false });
+ const DynamicComponent = dynamic(() => import('./Component'), {
+ ssr: false,
+ });
```
---
@@ -93,6 +106,7 @@ Performance is no longer an afterthought; it is actively graded by Google's sear
INP measures the latency of all user interactions (clicks, taps, and keyboard inputs) on a page, reporting the longest duration between the interaction and the next visual frame update. An ideal INP score is **under 200ms**.
**Optimization Strategies:**
+
1. **Break up Long Tasks**: Avoid running heavy synchronous JavaScript on the main thread. Use `setTimeout`, `requestIdleCallback`, or `scheduler.yield()` to yield control back to the browser.
2. **Optimistic Updates**: Update the UI instantly when an action is triggered, rather than waiting for an API response to complete.
3. **Memoize Expensive Calculations**: Use `useMemo` to cache filters and searches on large lists so typing in a search input doesn't block the main thread on every keystroke.
diff --git a/content/blog-post-2026-06-11/web-performance-in-2026-a-practical-guide.mdx b/content/blog-post-2026-06-11/web-performance-in-2026-a-practical-guide.mdx
index 9798834..ecdceb1 100644
--- a/content/blog-post-2026-06-11/web-performance-in-2026-a-practical-guide.mdx
+++ b/content/blog-post-2026-06-11/web-performance-in-2026-a-practical-guide.mdx
@@ -18,6 +18,7 @@ In this guide, we walk through a real-world performance case study, demonstratin
## 1. Case Study: The Sluggish Job Board
We audited a modern frontend job board, **OnlyFrontendJobs.com**, running Next.js 15 and React 19. While the site was beautifully designed, it suffered from three critical performance bottlenecks:
+
1. **Slow Job Details Page (TTFB > 800ms)**: Every single job posting page was rendered dynamically on demand, hitting the database on every request and bypassing the CDN.
2. **Massive HTML Payload (410 KB on Jobs Feed)**: The jobs listing page rendered 20 jobs but generated a huge HTML document, leading to severe DOM bloat.
3. **Main-Thread Blocking on Search Input (INP > 350ms)**: Typing in the search filter box caused typing lag and UI stuttering on mobile devices.
@@ -29,9 +30,11 @@ Here is exactly how we solved each of these issues.
## 2. Solution 1: CDN Edge Caching & Static Pre-rendering
### The Problem
+
The job details page (`/jobs/[slug]`) had no caching configured. Because it was a dynamic route, Next.js rendered the page on every single request, hitting the database, waiting for the query, and then returning the HTML.
### The Fix
+
We implemented **Incremental Static Regeneration (ISR)** and pre-rendered the most popular job pages at build time.
First, we added `export const revalidate = 1800` to the page file. This instructs the CDN (Vercel/Cloudflare) to cache the generated HTML at the edge for 30 minutes.
@@ -44,22 +47,27 @@ Second, we added `generateStaticParams` to statically pre-generate the 50 most r
// ISR: Cache job details at the CDN edge for 30 minutes
export const revalidate = 1800;
-import { getJob, getRecentJobSlugs } from './data'
+import { getJob, getRecentJobSlugs } from './data';
// Pre-render the 50 most recent jobs at build time for instant loading
export async function generateStaticParams() {
- const slugs = await getRecentJobSlugs()
- return slugs.map((slug) => ({ slug }))
+ const slugs = await getRecentJobSlugs();
+ return slugs.map((slug) => ({ slug }));
}
-export default async function JobDetailPage({ params }: { params: Promise<{ slug: string }> }) {
- const { slug } = await params
- const job = await getJob(slug)
+export default async function JobDetailPage({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+ const job = await getJob(slug);
// ... render page
}
```
### The Result
+
The Time to First Byte (TTFB) dropped from **800ms to under 50ms** for cached requests, making page transitions load **instantly** and completely shielding the database from traffic spikes.
---
@@ -67,37 +75,53 @@ The Time to First Byte (TTFB) dropped from **800ms to under 50ms** for cached re
## 3. Solution 2: Replacing Inline SVGs with Sprite Sheets
### The Problem
-The jobs page rendered a list of 20 jobs. Each job card rendered 8-10 inline SVGs (location pin, calendar, salary badge, hot/featured stars, etc.) using Lucide React.
+
+The jobs page rendered a list of 20 jobs. Each job card rendered 8-10 inline SVGs (location pin, calendar, salary badge, hot/featured stars, etc.) using Lucide React.
This resulted in **190 raw SVGs** being inlined directly in the HTML document, bloating the HTML payload size to **409.7 KB** and inflating the DOM node count to **1,113+ nodes**, which triggered Lighthouse warnings and degraded rendering speed.
### The Fix
+
We extracted all inline SVGs into a single, consolidated **SVG Sprite Sheet** (placed once at the root or loaded as an external asset) and referenced them inside components using the `