Next.js Performance Optimization: Practical Guide to Speed Up Your Apps
Learn how to speed up Next.js apps with image optimization, code splitting, static generation, font loading, and reducing bundle size.

Next.js 14/15 Upgrade Notes:
- Minimum Node.js version is now 18.17.
- The
next export
command is replaced byoutput: 'export'
innext.config.js
.- The
@next/font
package is fully removed; use the built-innext/font
.- APIs like
cookies
,headers
, anddraftMode
are now async in Next.js 15 (useawait
).fetch
requests are no longer cached by default (cache: 'force-cache'
if you need caching).- Some config options have changed (
experimental-edge
→edge
).- If you use TypeScript, update
@types/react
and@types/react-dom
.
See the official upgrade guide for more.
Building fast web apps is essential. Next.js provides many built-in performance features, but developers still need to use them wisely. In this guide, we'll cover practical techniques for speeding up Next.js apps. We'll dive into image optimization, code splitting and lazy loading, static generation, optimized font loading, and reducing JavaScript bundle sizes. Along the way, you'll find code examples and tips you can apply immediately.
Image Optimization
Large or unoptimized images are a common performance bottleneck. Next.js's built-in <Image>
component helps automatically optimize images for you. It serves correctly-sized images in modern formats (like WebP/AVIF) and enables lazy loading by default. For example, simply use <Image>
in your component:
1import Image from "next/image";
2
3export default function Hero() {
4 return (
5 <div>
6 <Image
7 src="/images/hero.jpg"
8 alt="Hero banner"
9 width={1200}
10 height={600}
11 priority
12 />
13 <p>Welcome to the site!</p>
14 </div>
15 );
16}
Image Component Features
- Responsive Images: The
<Image>
component automatically serves images in sizes best suited for the user's device. - Lazy Loading: Images are lazy-loaded by default, meaning they only load when they enter the viewport, which speeds up initial page load.
- Placeholder Blur: You can add a blur-up effect while the image is loading by using the
placeholder="blur"
prop. - Custom Loader: For advanced use cases, you can define a custom loader function to control how images are loaded.
Example: Responsive and Lazy-Loaded Image
Here's an example of an image that is both responsive and lazy-loaded:
1<Image
2 src="/images/hero.jpg"
3 alt="Hero banner"
4 layout="responsive"
5 width={1200}
6 height={600}
7 priority
8/>
Code Splitting and Lazy Loading
Code splitting is a technique to split your code into smaller bundles which can then be loaded on demand. Next.js does this automatically for pages, but you can also manually code-split components.
Dynamic Imports
Use dynamic imports to load components only when they are needed. This can be done using the next/dynamic
module. For example:
1import dynamic from "next/dynamic";
2
3const DynamicComponent = dynamic(() => import("../components/HeavyComponent"));
4
5export default function Page() {
6 return <DynamicComponent />;
7}
Route-Based Code Splitting
Next.js automatically splits your code at the page level. This means that each page only loads the JavaScript it needs. However, you can further optimize by using dynamic imports for components that are not immediately necessary.
Static Generation
Static generation is the preferred way to generate pages in Next.js. It pre-renders pages at build time, which means the HTML is generated and served as static files. This is great for performance and SEO.
getStaticProps
Use getStaticProps
to fetch data at build time. This function runs at build time in production and allows you to fetch data and pass it to your page component as props.
1export async function getStaticProps() {
2 const res = await fetch("https://api.example.com/data");
3 const data = await res.json();
4
5 return {
6 props: {
7 data,
8 },
9 };
10}
Incremental Static Regeneration (ISR)
Next.js 12 introduced ISR, which allows you to update static content after you’ve built your site. You can use the revalidate
property in getStaticProps
to specify how often a page re-generates.
1export async function getStaticProps() {
2 const res = await fetch("https://api.example.com/data");
3 const data = await res.json();
4
5 return {
6 props: {
7 data,
8 },
9 revalidate: 10, // Regenerate at most once every 10 seconds
10 };
11}
Optimized Font Loading
Fonts can be a significant part of your bundle size and can impact performance. Next.js 13 introduced the next/font
package to optimize font loading.
Using next/font
To use the new font system, import fonts from next/font
instead of using traditional CSS @font-face
rules. For example:
1import { Roboto } from "next/font/google";
2
3const roboto = Roboto({
4 subsets: ["latin"],
5 weight: "400",
6});
7
8export default function Page() {
9 return (
10 <div className={roboto.className}>
11 <h1>Hello, world!</h1>
12 </div>
13 );
14}
Benefits of next/font
- Automatic Subsetting: Only the characters used on the page are loaded.
- Variable Fonts: Support for variable fonts, which can reduce the number of font files needed.
- Optimized Loading: Fonts are loaded in a way that does not block the rendering of the page.
Reducing JavaScript Bundle Size
A smaller JavaScript bundle means faster load times. Here are some strategies to reduce your bundle size:
- Analyze your bundle: Use the
next/bundle-analyzer
to see what's in your JavaScript bundle and identify opportunities to reduce its size. - Remove unused dependencies: Regularly audit your dependencies and remove any that are not being used.
- Use lighter alternatives: For example, use
date-fns
instead ofmoment.js
for date manipulation, as it's significantly smaller. - Optimize lodash imports: Instead of importing the entire lodash library, import only the functions you need.
Example: Using next/bundle-analyzer
To use the bundle analyzer, install it first:
npm install @next/bundle-analyzer
Then, update your next.config.js
:
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
module.exports = withBundleAnalyzer({});
Now, you can analyze your bundle by running:
ANALYZE=true next build
Conclusion
Performance optimization is critical in delivering a fast, user-friendly web experience. Next.js offers powerful features to help developers optimize their applications effectively. By leveraging image optimization, code splitting, static generation, optimized font loading, and diligent bundle size management, you can significantly enhance the performance of your Next.js applications. Remember to stay updated with the latest Next.js releases and best practices to keep your applications running smoothly and efficiently.