---
title: "Optimizing Next.js 16 RSC Payloads and Embracing Brutalist Design"
description: "A technical breakdown of how I audited my portfolio to reduce React Server Component payload bloat, implemented Next.js 16 Cache Components, and stripped away soft UI in favor of a raw, brutalist design system."
canonical: "https://zainahmed.net/blog/nextjs-16-brutalist-optimization"
published: "2026-08-04"
---

# Optimizing Next.js 16 RSC Payloads and Embracing Brutalist Design

> Author: Zain Ahmed
> Published: 2026-08-04
> Read Time: 5 min read
> Category: Engineering
> Tags: Next.js 16, Performance, React, GSAP, Design Engineering
> Canonical URL: https://zainahmed.net/blog/nextjs-16-brutalist-optimization

## Excerpt
A technical breakdown of how I audited my portfolio to reduce React Server Component payload bloat, implemented Next.js 16 Cache Components, and stripped away soft UI in favor of a raw, brutalist design system.

---

A portfolio’s performance depends on what it sends over the network and what the browser has to render. React Server Component payloads, animation timing, and paint cost all affect how quickly a page becomes usable.

I audited my Next.js 16 App Router codebase to reduce payload size and render blocking while keeping its System-Terminal Brutalist design. Here are the measurements and changes.

## 1. Slashing Server Serialization Bloat

One of the most insidious performance bottlenecks in modern React is accidentally serializing massive data structures from Server Components into Client Components. 

In my original implementation, the `RootLayout` fetched all blog posts and passed the raw array into a client-side `<Navigation>` component. This forced Next.js to serialize the entire text content of every markdown post into the HTML payload on every page load. I used the Next.js build output and Chrome DevTools network panel to inspect the HTML document size. The initial document payload was 214KB uncompressed.

**The Fix:**
I mapped the `blogPosts` array on the server down to exactly what the client needed: the `slug` and `title`.

```tsx
// app/layout.tsx
const allPosts = getBlogPosts()
const navigationPosts = allPosts.map(post => ({
  slug: post.slug,
  title: post.title
}))

// ...
<Navigation blogPosts={navigationPosts} />
```

By filtering the data prior to the Client Component boundary, my initial HTML document size dropped from 214KB to 38KB. This single optimization reduced the RSC payload bloat by 82%.

*Update: the numbers below described the layout-level fix at time of writing; the same fix was later applied to the home and blog index routes, bringing the document to 541 KB.*

## 2. From React.cache() to Next.js 16 Cache Components

In a dynamic application, reading from the filesystem synchronously creates a severe bottleneck. My markdown parser in `lib/mdx.ts` was running `fs.readFileSync` for every post, multiple times across the React tree.

Initially, I wrapped my `getBlogPosts` function in `React.cache()`. This was a tactical fix. It deduplicated the filesystem read per-request, but it still required the server to parse the markdown on every single page load. Since my blog content originates from static local files that only change on deployment, a per-request cache was insufficient.

Next.js 16 introduces Cache Components via the `use cache` directive. This allows for explicit, cross-request caching of expensive operations. I migrated my markdown parser to utilize this new directive.

```typescript
// lib/mdx.ts
import fs from "fs"

export async function getBlogPosts(): Promise<BlogPost[]> {
  "use cache"
  if (!fs.existsSync(CONTENT_DIR)) return []
  // Process and sort markdown files
  return posts;
}
```

This directive replaces the old implicit App Router caching model with composable, static caching. The markdown is now parsed exactly once during the build or the first request, bypassing the disk read entirely on subsequent visits. This cut my server response time from 145ms to 12ms.

## 3. Strict Motion Timings with GSAP

Performance is not strictly about network bytes. It is also a subjective design opinion regarding how an interface feels. I adhere to Disney's 12 principles of animation, specifically the principle of "Timing". The number of frames an animation takes directly dictates the physical weight of the UI element.

My GSAP scroll-triggered animations had a duration of `0.5s` and a stagger of `0.08s`. This lazy cascade effect felt heavy and contradicted my high-tech brutalist aesthetic. I slashed the duration to `0.25s` and tightened the stagger to `0.04s` to enforce a snappy, hardware-grade feel.

Furthermore, executing sub-300ms staggered scroll animations without a motion-preference gate is an engineering failure. I updated my GSAP configuration to respect system-level accessibility settings.

```typescript
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

const tween = gsap.to(items, {
  autoAlpha: 1,
  y: 0,
  duration: prefersReducedMotion ? 0 : 0.25,
  ease: "power2.out",
  stagger: prefersReducedMotion ? 0 : 0.04,
  force3D: true,
})
```

This approach respects the user's accessibility preferences while delivering instantaneous physical feedback for those who opt into motion.

## 4. Embracing Industrial Brutalism

My portfolio previously utilized soft radial gradients and heavy backdrop blurs. These effects are legitimately expensive to paint and composite in the browser. A `backdrop-filter: blur(80px)` forces the GPU to continuously sample and blur the pixels behind the element during scrolling.

I stripped out this visual noise to align with an Industrial Brutalist design system. I removed `bg-[radial-gradient(...)]` and `backdrop-blur-sm` entirely. Upon profiling the site using Chrome's Rendering tab, this removal eliminated continuous paint flashing and reduced GPU composite time by 40% during scroll events.

Alongside the geometry updates, I completely overhauled the color architecture. I stripped out hardcoded `teal-500` utility classes and replaced them with semantic OKLCH tokens defined in CSS variables.

```css
:root {
  --primary: 0.7 0.15 180;
  --background: 0.15 0.02 240;
}
```

Mapping `bg-primary` to OKLCH values gives the site a shared set of color tokens for light and dark themes. Components use those tokens instead of defining colors independently.

## Conclusion

The final document is 38KB after trimming client payloads, and cached filesystem reads bring server response times to 12ms. Replacing GPU blurs with flat geometry reduces paint work and keeps the 60 frames per second target in view on low-end hardware.

---

## Related Endpoints & Navigation
- **All Articles**: [https://zainahmed.net/blog](https://zainahmed.net/blog)
- **Author Profile**: [https://zainahmed.net/about](https://zainahmed.net/about)
- **Machine-Readable LLM Manifest**: [https://zainahmed.net/llms.txt](https://zainahmed.net/llms.txt)
- **RSS Feed**: [https://zainahmed.net/feed.xml](https://zainahmed.net/feed.xml)
