
Static vs Dynamic: Reading the Next.js Build Output
Do you know what the Circle (○) and Lambda (λ) symbols mean in Next.js build logs? Ensure you aren't accidentally making every page dynamic.

Do you know what the Circle (○) and Lambda (λ) symbols mean in Next.js build logs? Ensure you aren't accidentally making every page dynamic.
Establishing TCP connection is expensive. Reuse it for multiple requests.

Optimizing by gut feeling made my app slower. Learn to use Performance profiler to find real bottlenecks and fix what matters.

Definitely No, Maybe Yes. How to check membership in massive datasets with minimal memory using Bit Arrays and Hash Functions. False Positives explained.

Text to Binary (HTTP/2), TCP to UDP (HTTP/3). From single-file queueing to parallel processing. Google's QUIC protocol story.

I vividly remember the day I launched my first e-commerce site built with Next.js. Feature development was flawless. Payments worked, the cart was smooth, and the design was pixel-perfect. I deployed it to an AWS EC2 t3.small instance and typed the domain with trembling hands.
Enter.
... ... ... (3 seconds) ... (5 seconds)
The screen appeared. After 5 full seconds.
"Wait, it was instant on localhost!" I checked the server resources—CPU was hitting 100%. With just one user (me). Even the simple Landing Page took over 2 seconds to load. Images were optimized, fonts subsetted. What the hell was wrong?
After pulling an all-nighter, I stared blankly at the build logs I had ignored.
Route (app) Size First Load JS
┌ λ / 5.4 kB 89 kB
├ λ /about 2.1 kB 84 kB
├ λ /blog/[slug] 3.5 kB 87 kB
└ λ /products 1.5 kB 83 kB
Every route had a λ (Lambda) symbol next to it.
I thought it was just the Next.js logo or something cool. But it was actually a warning: "Your site is running at worst-possible performance."
I thought I built a Static Site. In reality, I had built a site where every single page was Server-Side Rendered (SSR).
The summary output of npm run build isn't just noise. It is a Report Card that determines your server bills and user churn rate.
Understanding these symbols can boost performance by 10x without spending a dime.
"Automatically rendered as static HTML (uses no initial props)"
index.html file via Nginx (or CDN)."Automatically generated as static HTML + JSON (uses getStaticProps)"
getStaticProps or generateStaticParams. The result is still a static file."Server-side renders at runtime (uses getInitialProps or getServerSideProps)"
My logs were all λ. Even the static Company Info page (/about).
Basically, I wasn't using Next.js properly; I was using a confusing, expensive version of PHP.
"Wait, I never used getServerSideProps! I only used fetch in the App Router!"
I felt cheated. Didn't Next.js 13 advertise "Static by Default"? But Next.js has a trap called "Dynamic Functions." If you use any of specific functions anywhere in your code, Next.js demotes that entire page to Dynamic.
cookies() and headers() (Most Common Mistake)To show a different Global Navigation Bar (GNB) based on login status, I wrote this in layout.tsx:
// app/layout.tsx
import { cookies } from 'next/headers';
export default function RootLayout({ children }) {
const cookieStore = cookies(); // <-- THE CULPRIT!
const accessToken = cookieStore.get('accessToken');
return (
<html>
<body>
<Navbar isLoggedIn={!!accessToken} />
{children}
</body>
</html>
);
}
This code is a disaster.
RootLayout wraps every page. Calling cookies() here declares: "Every page in this site depends on the request (cookie)."
Consequently, /, /about, /blog—everything turns into λ (Dynamic). Static optimization is wiped out.
searchParams PropsIn a blog list page, I accessed searchParams to get the current page number.
// app/blog/page.tsx
export default function Blog({ searchParams }) { // <-- CULPRIT
const page = searchParams.page || '1';
// ...
}
Query strings like ?page=2 differ per request. Next.js sees this and says, "Oh, you use query strings? Can't pre-build that." -> Dynamic Switch.
Is this reasonable? Page 1 of a blog list is always the same. It doesn't need to be Dynamic.
fetch with no-storefetch('https://api.example.com/posts', { cache: 'no-store' });
"I want the latest posts," so I mindlessly disabled the cache. I gained data freshness but lost page load speed. This single line forces the entire page to SSR.
Knowing the problem, I tore down and rewrote the code. The goal: "Make everything static except personalized pages."
cookies() to Client ComponentsDo NOT read cookies in Server Components (layout.tsx).
If you need to change UI based on login, use a Client Component with useEffect or a dedicated library (Auth.js SessionProvider).
// app/layout.tsx (Server Component)
// cookies() removed!
export default function RootLayout({ children }) {
return (
<html>
<body>
<AuthProvider> {/* Client Component */}
<Navbar />
</AuthProvider>
{children}
</body>
</html>
);
}
Now RootLayout can be static again. Authentication checks happen only in the browser (Client).
generateStaticParams AggressivelyUsing a dynamic route ([slug]) doesn't strictly mean λ. You just need to inform Next.js of "all possible paths" in advance.
// app/blog/[slug]/page.tsx
// This function is key!
export async function generateStaticParams() {
const posts = await getPosts(); // Get all slugs from DB
return posts.map((post) => ({ slug: post.slug }));
}
export default function Post({ params }) { ... }
With this, Next.js runs getPosts() at build time and pre-bakes HTML for blog/1, blog/2, blog/3... (●).
force-static)The ultimate weapon.
"I read headers() (e.g., to check IP), but if it's empty during build, just use a default value! Please make it static!"
For these hybrid cases:
export const dynamic = 'force-static';
Adding this line to the top of the page forces a static build, making dynamic functions (cookies(), headers()) return empty/undefined during the build process instead of opting out of static generation.
After days of refactoring, I ran the build again.
Route (app) Size First Load JS
┌ ○ / 5.4 kB 89 kB
├ ○ /about 2.1 kB 84 kB
├ ● /blog/[slug] 3.5 kB 87 kB
├ ○ /login 1.5 kB 83 kB
└ λ /my-page 1.2 kB 80 kB
Finally!
Main, About, Blog, Login—all returned to ○ (Static) or ● (SSG). Only /my-page remained λ (Dynamic), which is expected as it's personalized.
Performance test after deployment?
Next.js doesn't make your site fast automatically. Used wrong, it can be slower than PHP.
npm run build logs before deploying.
cookies(), headers(), and searchParams are 90% likely to be the silent killers. Trap them inside Suspense boundaries or banish them to the Client side.Love the ○ and ●. Allow λ only when absolutely necessary. That is the only way to protect both your users' patience and your wallet.