
Join the Conversation!
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
A common saying is, “Don’t obsess over micro-optimization too early. First, build it to work—then make it fast where it matters .”
This is exactly why I didn’t start teaching you how to write ultra-optimized code from the beginning. While I did sprinkle in some optimization techniques — like making parallel requests or using React Suspense — they weren’t fully utilized. That was intentional. Those techniques were introduced to give you a taste of what’s coming.
Now that you’ve built something real, it’s time to step back and think strategically.
Of course, there are many ways through which you can optimize. Some of these strategies are,
You already know why Server Components matter—better performance, reduced bundle size, etc. No need to repeat myself here 😉
There have been a couple of instances where you have already learned the pros and cons of implementing parallel data fetching. But here is a little recap for you.
Parallel requests or fetching means requesting multiple resources at the same time instead of waiting for one before starting the next.
It helps you skip the “waterfall” effect, where one request waits for another to finish, cutting down the total load time for pages that need multiple resources.
Example,
const [user, posts] = await Promise.all([
getUser(),
getPosts(),
]);
All the requests start at the same time, so you only wait for the slowest one to finish — not all of them one after another.
You know it really well by now. You learned about this in previous lessons and recently in the Loading & Errors module as well.
It lets you render parts of your page immediately while other parts are still loading, thus improving performance (FCP) by showing content quickly and avoiding showing blank screens to users until the whole data is ready.
Example,
import { Suspense } from 'react';
import StatsSummary from './StatsSummary';
import AllAnswers from './AllAnswers';
export default function DashboardPage() {
return (
<div>
<StatsSummary />
<Suspense fallback={<div>Loading all answers...</div>}>
<AllAnswers />
</Suspense>
</div>
);
}
React gives you a handy feature that helps your server components avoid repeating the same work. It remembers (or “memoizes”) the results of function calls, so it doesn’t do the same thing twice.
But you may think, how does it actually work?
Yep — it was during the dynamic metadata lesson. You didn’t want to fetch the same question data twice using getQuestion, so you wrapped that function with React’s cache to reuse the result instead.
React cache comes in handy for things like fetching data, running heavy calculations, or sharing results across components without repeating the same work.
But how do you use it?
As stated above, you just have to wrap your function in cache like this,
import { cache } from 'react';
// Create a cached version of the function
export const getData = cache(async (userId) => {
// This expensive operation will only run once per userId
const data = await fetchUserData(userId);
return data;
});
And then you’re good to call the getData function like any normal function in any of your components.
It’s that simple.
Unlike useMemo, which is for client components, cache is specifically designed for server-side rendering optimization. Keep that in mind.
While it works differently under the hood, Next.js also has its own caching system—similar in spirit to React’s.
And honestly, by the time you’re reading this, there’s a good chance Next.js has already rolled out a shiny new caching update 😅
Anyway, so Next.js offers different types of cache functionality starting from,
With this, results of data fetch (I'm talking about fetch API here) across server requests and deployments.
Example,
// With caching (default) (SSG alike)
const data = await fetch('https://api.example.com/data')
// With time-based revalidation (ISR alike)
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // Revalidate every hour
})
// Without caching (SSR alike)
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
It’s similar to the rendering strategies you learned earlier in the course, but the key difference here is that this applies at the data or request level, rather than the page level.
This is a page/route-level cache, meaning Next.js stores the rendered output (RSC Payload and HTML) for entire routes or pages. It caches the whole route or page.
It’s like applying a single rendering strategy to the entire page. By default, Next.js caches the rendered result of routes on the server (SSG), and these static pages are cached at build time.
However, dynamic routes that rely on searchParams, headers, or cookies are rendered at request time and aren’t cached, which is essentially SSR.
It’s an in-memory cache that stores the React Server Component Payload of route segments.
Basically, Next.js caches previously visited routes and prefetches any other routes linked through the Link component on that page.
If you'd prefer not to prefetch certain routes, you can disable it by adding these props to your Links.
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard" prefetch={false}>Dashboard</Link>
}
Keep in mind that by default, Next.js automatically prefetches all the links on that page in the background.
I have an in-depth video on these advanced Next.js concepts, covering how caching works, where it's stored, and more, in the next lesson. Make sure not to skip it.
In addition to these core concepts, you can manually cache things, particularly server action results, using Next.js's new "use cache" directive. It's currently a canary feature, so I won’t go into detail just yet. Once it becomes widely available, I'll create a dedicated lesson on it in this module.
To give you a quick idea, with this directive, you can cache a route, component, or specific function—just by marking it with "use cache" 😃
These are just a few common widely optimization techniques used in Next.js applications. There are others as well, such as image optimizations (using <Image /> where appropriate, but not everywhere), font optimizations with next/font (which you've already implemented), securing server actions (which we've covered), and avoiding unnecessary client-side code.
The good news is that most of these optimizations are already supported by Next.js out of the box. You just need to understand how they work—and now you do!
So go ahead, take a deep look at your whole codebase, and think about what you can optimize further based on shared strategies. Can you make any page complete SSG or maybe ISR or keep it SSR? Or cache a certain server action? or use Suspense. Think.
How do I remove the blur effect from my CSS?
I removed but the blur is still there. Any ideas?
filter: blur(5px);
Does work for removing blur from modals?
backdrop-filter: none;
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.