How to optimize Next.js projects for better performance

Optimize Next.js.jpeg
By Ashita G
Read time 11 min
Posted on 8 Apr 2026

Web performance used to be about survival; today, it’s about speed, SEO, and user trust. Even with powerful tools like Next.js, your app won’t be fast by default. In this guide, I’ll break down how to truly optimize Next.js using smart rendering strategies, code splitting, lazy loading, and more, so your users never have to wait.

From dial-up to Core Web Vitals: How web performance has evolved

In the mid-1990s, the internet was much different from today's high-speed, continuously connected world. Back then, performance wasn’t about optimization but about survival. A 3 MB single webpage would take around 10 minutes to load completely on a 56k connection - assuming a connection that has no interruptions. When text was king and images were rare, minimalism was a necessity as each element had to justify the bandwidth cost. 

Developers earlier had to learn how to compress images ruthlessly, minimize HTML to reduce load times, create text-only web versions, and use image maps to reduce the number of separate image files.

As websites grew in their complexity, the need for performance measurement became apparent. Metrics were more technical in nature, without much focus on user experience, such as:

  • Page load time: Time it takes from initial request to complete page render.

  • Time to first byte(TTFB): How the server responds to requests.

  • Number of HTTP requests: The higher the number of requests, the slower the page loads.

Research has consistently shown that users form an impression within milliseconds, as factors are largely dependent on how fast and smoothly the website loads. A quick load time leads to a better conversion rate, whereas a slow load time leads to an exponential increase in abandonment rate.

When users face performance issues and are less likely to return, the damages aren’t just short-term but also long-term. This is evident by the way search engines impose penalties after evaluating website quality based on poor engagement metrics, which creates a negative feedback loop where low rankings lead to reduced traffic, ultimately impacting business growth.

Core Web Vitals: The gold standard

Core Web Vitals are a set of performance metrics introduced by Google to replicate real-world user experience on a website, making them essential for SEO and user satisfaction. 

The 4 major metrics that Google established to capture the critical aspects of a website's performance are:

  • FCP

  • LCP

  • CLS

  • TBT

Let’s understand what each of these terms means.

First Contentful Point (FCP)

FCP measures how long it takes for the browser to render the first piece of content from the DOM. This is an indication of what users get to see first when they are on a page, thus providing an immediate visual feedback. First image, non-white canvas element, first text content, and SVG elements are DOM contents that trigger FCP.

The performance threshold for the FCP score (regardless of the device) is:

FCP score

Largest Contentful Point (LCP)

LCP measures how long it takes for the largest visible element in the DOM to be fully rendered. This metric reflects how users perceive when the main content of the page has finally loaded.

Large images like Hero image or banner, video poster image, large text blocks, and background images are loaded via CSS.

The performance threshold for the LCP score is:

LCP score

Cumulative Layout Shift (CLS)

CLS quantifies how much of your visible webpage content shifts during the loading process to the user. Unexpected layout shifts impact user experience, especially when it cause misclicks or when a text moves suddenly.

How is it calculated?

CLS is the product of two measurements: Impact fraction and Distance fraction. Impact fraction is calculated based on how much the viewport has been affected by the shift, whereas Distance fraction tells how far elements have moved relative to the viewport. 

CLS = Impact fraction * Distance fraction

The performance threshold for the CLS score is:

CLS score

Total Blocking Time (TBT)

TBT measures how long the page is blocked from responding to user input, such as mouse clicks or keyboard presses.TBT strongly correlates with metrics such as First Input Delay(FID) and Interaction to Next Paint(INP). 

TBT works by identifying tasks that take up more than 50ms and sums up all blocking time between FCP and TTI.

E.g., a high TBT score indicates that users will experience delays when interacting with the webpage.

The performance threshold for the TBT score is:

TBT score

Next.js for performance optimization

Fortunately, Next.js provides powerful tools to optimize Core Web Vitals. Being a React-based framework, it streamlines the development of high-performance applications by providing powerful features such as hybrid rendering, code splitting, and asset optimization, making it a suitable choice when you want to build fast and SEO-friendly websites.

Rendering options: Techniques to optimize Next.js 

Rendering in Next.js is the process of transforming React components into HTML web pages that the browser can display. To determine when and where the HTML is created, various strategies are used, with the most popular being:

  • Static Site Generation(SSG)

  • Server-Side Rendering(SSR)

  • Incremental Static Regeneration(ISR).

SSG:

Static Site Generation(SSG) is a method whereby webpages are pre-built and generated as static HTML during the build process.

The HTML content is created ahead of time and stored as a static file so that whenever a user requests them, a pre-generated HTML file is rendered without the need for processing on the server side during request time.

How does SSG work?

Static Site Generation(SSG)

1. Before deployment, Next.js fetches necessary data from APIs and databases.

2. HTML pages get generated with all content embedded.

3. The HTML files, along with CSS and JavaScript, are saved as static files.

4. Files are deployed to the CDN for global distribution.

5. When a user visits a website,pre-built HTML files are received immediately. 

Example:

import React from 'react'; import { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { bcmsPrivate } from '@/bcms-private'; import { bcmsPublic } from '@/bcms-public'; import { ProductEntry, ProductEntryMetaItem } from '@bcms-types/types/ts'; import ProductDetailView from'@/components/product-page/ProductDetailView'; export async function generateMetadata({   params, }: {   params: { slug: string }; }): Promise<Metadata> {   const productEntry = (await bcmsPrivate.entry.getBySlug(     'product',     params.slug,   )) as ProductEntry;   if (!productEntry) {     return notFound();   }   const productMeta = productEntry.meta.en as ProductEntryMetaItem;   
  const pageTitle = `${productMeta.seo?.title || productMeta.title} | E-commerce Store`;   const pageDescription = productMeta.seo?.description || productMeta.description;   
const ogImageUrl = productMeta.images?.[0]?.url    ? bcmsPublic.media.getMediaUrl({        slug: productMeta.images[0].slug,        crop: { width: 1200, height: 630 },       })    : undefined;  return {    title: pageTitle,    description: pageDescription,    openGraph: {      title: pageTitle,      description: pageDescription,      images: ogImageUrl ? [ogImageUrl] : [],      url: `https://your-ecommerce.com/products/${params.slug}`,     },    twitter: {      card: 'summary_large_image',      title: pageTitle,      description: pageDescription,      images: ogImageUrl ? [ogImageUrl] : [],    },  };}exportasyncfunctiongenerateStaticParams() {  const productEntries = (await bcmsPrivate.entry.getAll('product')) as ProductEntry[];  if (!productEntries) {    return [];  }  const params = productEntries.map((entry) => ({    slug: entry.meta.en.slug,   }));  return params;}const ProductPage: React.FC<{ params: { slug: string } }> = async ({ params }) => {  const productEntry = (await bcmsPrivate.entry.getBySlug(    'product',    params.slug,  )) as ProductEntry;  if (!productEntry) {    return notFound();  }  const productMeta = productEntry.meta.en as ProductEntryMetaItem;  return (    <div>      <ProductDetailView        product={productMeta}        bcmsConfig={bcmsPublic.getConfig()}      />    </div>  );};

export default ProductPage;

Benefits:

1. Since pages are pre-built, they load instantly. This not only enhances user experience but also reduces bounce rate.

2. Excellent for SEO as pre-rendered HTML content makes it easier for index crawling and leads to better search engine ranking and visibility.

3. With no server-side processing at runtime, the attack surface is greatly reduced. This makes SSG sites much more secure.

4.  Deploying static sites is much easier since generated files are pushed to a CDN or static hosting provider. 

5. Ideal for content that doesn’t update frequently, such as blogs and marketing pages. 

Limitations:

1. Requires rebuilding the entire application even for small changes.

2. Content can become stale or outdated between builds.

3. Cannot handle real-time data, making it unsuitable for dynamic content.

SSR:

Server-Side Rendering(SSR) is a method where webpages are generated on the server in real time when a user requests them each time.

When a server receives a user request, it fetches the data, executes the code, and generates an HTML page dynamically. This newly created HTML page is then sent to the user’s browser. 

How does SSR work?

SSR

1. The user requests a page from their browser.

2. Next.js server receives requests and starts processing.

3. Server fetches fresh data for each request from APIs or external services, and databases, which can affect overall performance.

4. Server generates an HTML with the latest data embedded.

5. HTML is sent to the user’s browser, and hydration takes over for client interactivity. 

Example:

pages/products/[slug].tsx
import React from 'react'; import { GetServerSideProps } from 'next'; import Image from 'next/image'; import { bcms } from '../../src/bcms'; import { ProductEntry } from '../../bcms/types/ts'; // Import your product type import { BCMSContentManager } from '@thebcms/components-react'; // For rich text content interface ProductDetailPageProps {   product: ProductEntry | null; } const ProductDetailPage: React.FC<ProductDetailPageProps> = ({ product }) => {   if (!product) {     return (       <div style={{ padding: '20px', textAlign: 'center' }}>         <h1>Product Not Found</h1>         <p>The requested product could not be found.</p>         <Link href="/products">Back to Products</Link>       </div>     );   } // Safely access properties, assuming 'en' locale   const title = product.meta.en?.title || 'Unknown Product';   const description = product.meta.en?.description || [];   const price = product.meta.en?.price;
  const imageUrl = product.meta.en?.image?.src;   const imageAlt = product.meta.en?.title || 'Product Image';
return ( 
  <div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px', border: '1px solid #eee', borderRadius: '8px' }}>       <h1>{title}</h1>       {imageUrl && (         <div style={{ textAlign: 'center', marginBottom: '20px' }}>           <Image             src={imageUrl}             alt={imageAlt}             width={400}             height={400}             objectFit="contain"             style={{ borderRadius: '8px' }}           />         </div>       )}       <p style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#333' }}>         Price: ${price?.toFixed(2) || 'N/A'}       </p>       <h2 style={{ marginTop: '30px', borderBottom: '1px solid #ddd', paddingBottom: '10px' }}>Description</h2>       <BCMSContentManager content={description} /> {/* Renders rich text */}       <div style={{ marginTop: '30px', textAlign: 'center' }}>         <Link href="/products" style={{ display: 'inline-block', padding: '10px 20px', backgroundColor: '#0070f3', color: 'white', textDecoration: 'none', borderRadius: '5px' }}>           Back to All Products         </Link>       </div>     </div>   ); };
export const getServerSideProps: GetServerSideProps<ProductDetailPageProps> = 
async (context) => {   const { slug } = context.params as { slug: string };   try {
  const product = (await bcms.entry.getBySlug(slug, 'product')) as ProductEntry | null;     if (!product) {       return {         notFound: true,       };     }     return {       props: {         product,       },     };   } catch (error) {     console.error(`Error fetching product with slug "${slug}" from BCMS:`, error);     return {       notFound: true,     };   } }; export default ProductDetailPage;

Benefits:

1. Since pages are rendered on demand, users always get the most up-to-date content, which is a crucial aspect for dynamic data.

2. Allows you to serve unique content based on users' preferences.

3. With the server doing all the heavy lifting of rendering, processing is reduced on clients' devices, making it advantageous for clients with older devices.

4. Similar to SSG, even SSR provides excellent SEO for dynamic content, as search engines can index the latest information.

5. Each time changes are made to the CMS or database, SSR automatically reflects those changes in the next user request without the need for a full site rebuild.

Limitations:

1. The server must process each user request, potentially leading to slower response times.

2. A costly approach since each request to the server makes it resource-intensive.

3. An increase in server load due to traffic could lead to challenges in scaling.

ISR: The Hybrid Approach

ISR (Incremental Static Regeneration) is a powerful feature of Next.js that lets you combine the best of both: SSG + SSR. It allows you to update or create static pages after you’ve built the site without rebuilding the entire application.

How does Regeneration work?

ISR

When a revalidate value is set, Next.js undergoes the following process:

1. The user gets a statistically generated page created during the build process.

2. During subsequent requests, if the page is older than the revalidate time, the user still gets a cache version immediately.

3. Simultaneously, Next.js regenerates the page in the background. 

4. Once regeneration is complete, the cache gets upgraded with a new version, and future requests get an updated page.

Example:

export const revalidate = 3600; import React from 'react'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { Metadata } from 'next'; import { BCMSImage } from '@thebcms/components-react'; import { bcms } from '@/app/bcms-client'; import ContentManager from '@/components/ContentManager'; import ProductCard from '@/components/product/Card'; 
import { ProductEntry, ProductEntryMetaItem } from '../../../../bcms/types/ts'; import { EntryContentParsedItem } from '@thebcms/types'; import { toCurrency } from '@/utils/currency'; 
type Props = {     params: {         slug: string;     }; }; export async function generateStaticParams() {     const products = (await bcms.entry.getAll('product')) as ProductEntry[];     return products.map((product) => {         const meta = product.meta.en as ProductEntryMetaItem;         return {             slug: meta.slug,         };     }); } export async function generateMetadata({ params }: Props): Promise<Metadata> {     const product = (await bcms.entry.getBySlug('product', params.slug)) as ProductEntry;     if (!product) {         return notFound();     }     const meta = product.meta.en as ProductEntryMetaItem;     const pageTitle = `${meta.title} - Acme Corp`;     return {         title: pageTitle,         description: meta.description,         openGraph: {             title: pageTitle,             description: meta.description,             images: [                 {                   url: meta.cover_image.src,        
         },             ],         },         twitter: {             card: 'summary_large_image',             title: pageTitle,             description: meta.description,             images: [meta.cover_image.src],         },     }; } const ProductPage: React.FC<Props> = async ({ params }) => {     const product = (await bcms.entry.getBySlug('product', params.slug)) as ProductEntry;     if (!product) {         return notFound();     }     const { meta, content } = {         meta: product.meta.en as ProductEntryMetaItem,         content: product.content.en as EntryContentParsedItem[],     };     const otherProducts = ((await bcms.entry.getAll('product')) as ProductEntry[])         .filter((p) => p.meta.en?.slug !== params.slug)         .slice(0, 3);
    ); }; export default ProductPage;

Benefits:

1. Unlike SSR, pages are regenerated only when it’s necessary, not on each user request. 

2. ISR can handle large amounts of content pages efficiently and making it suitable for applications that require scalability, like ecommerce websites.

3. Provides flexible revalidation strategies, giving developers control over content freshness.

4. Gives a near-perfect balance between speed and updated content as it combines the approaches of SSG and SSR.

5. Utilizes a ‘stale-while-revalidate’ mechanism where stale cache gets replaced only by a new version that is ready.

Limitations:

1. Managing when and how to update content can be challenging for cache invalidation. 

2. Different users could see different versions of the application.

3. Difficulty in debugging since it’s harder to predict which version users will see.

Optimizing static assets - best practices to optimize performance

In typical web applications, components such as images, videos, and fonts make up 60% of the total page weight and have a direct influence on metrics such as FCP, LCP, and CLS.

An unoptimized single banner image can push your LCP from 2.5 seconds to over 4 seconds, while improperly loaded fonts can cause significant shifts in layout.

Starting with  v.10., Next.js has taken an ‘optimization by default’ approach to asset handling by providing built-in components that work seamlessly out of the box. While integrated toolchains can handle most optimization automatically, such as converting images to a modern web format, Next.js also allows developers to fine-tune control when needed.

Font Optimization

Font optimization uses techniques and built-in features to improve web performance by efficiently loading, managing, and displaying fonts. The next/font module optimizes fonts automatically for better security and performance. Fonts can be imported using next/font/google or next/font/local.

How does next/font/google work under the hood?

1. During build time, Next.js downloads the font file from Google Fonts.

2. Fonts are then served from your domain instead of Google’s CDN.

3. Creates an optimized CSS with appropriate font-face declarations.

4. For critical fonts, automatic <link rel="preload"> tags are added.

5. Calculates precise font metrics to prevent layout shift.

6. Only includes the subset specified.

Example:

//app/layout.tsx import { Inter } from 'next/font/google' const inter = Inter({   subsets: ['latin'], })
export default function Layout({ children }: { children: React.ReactNode }) { 
  return (     <html lang="en" className={inter.className}>       <body>{children}</body>     </html>   )}

How different is the next/font/local process?

next/font/local follows a similar approach except that rather than using an external source like Google Fonts, Next.js allows developers to load custom local fonts by specifying src from local files(e.g., .woff2, .woff, .ttf).

Example:

import localFont from 'next/font/local'; // Load the font from the local fonts folder
const inter = localFont({  src: '../fonts/Inter-Regular.woff2',  display: 'swap', // better for performance  weight: '400',      style: 'normal'});exportdefaultfunctionHome() {  return (    <main className={inter.className}>      <h1>Hello from Next.js with a local font!</h1>    </main>  );}

Video Optimization

Unlike Next.js' built-in feature for fonts and images, there isn’t a native next/video component yet. However, there are techniques and third-party services available that can optimize videos in your Next.js application.

If you look at the code snippet provided in the Next.js documentation. The  Video component renders an HTML <video>  element with a source file that has specific width, height, and controls for playback, including a subtitle track and fallback message for unsupported browsers. 

export function Video() { 
 return (   <videowidth="320"height="240"controlspreload="none">     <sourcesrc="/path/to/video.mp4" type="video/mp4" />   <track   src="/path/to/captions.vtt"           kind="subtitles"       srcLang="en"   label="English"      />      Your browser does not support the video tag.    </video>  )}

Although the code is functional, it can be optimized for better performance and accessibility such as:

  • Making responsive dimensions: Using CSS or a utility framework like Tailwind would make the video more responsive. 

export function Video() {   return (     <video       className="w-full h-auto max-w-full aspect-video"       controls       preload="none"     >       <source src="/path/to/video.mp4" type="video/mp4" />       <track         src="/path/to/captions.vtt"         kind="subtitles"         srcLang="en"         label="English"       />       Your browser does not support the video tag.     </video>   ); }

Instead of hard-coding values for the height and width of the video, Tailwind classes like w-full for the full width of the parent container, h-auto for adjusting the height to maintain the video’s aspect ratio, max-w-full to prevent the video from overflowing its container and aspect-video to apply a 16:9 aspect ratio will adjust the video across different devices.

  • Support for multiple video sources: The component specifies only one video format. However, different browsers and devices may support other formats like WebM. Adding multiple sources to the <video>  component ensures better compatibility. 

<source src="/path/to/video.webm" type="video/webm" /><source src="/path/to/video.mp4" type="video/mp4" />
  • Compressing video: Embedding videos can lead to an increase in bandwidth usage and load time which is why it is essential to encode the video with H.265(HEVC) or AV1 for MP4 or VP9 for WebM to achieve better compression without losing the quality of the video. This can be done with the help of a popular tool like FFmpeg.

ffmpeg -i input.mp4 -c:v libx265 -crf 28 -c:a aac -b:a 128k output.mp4

FFmpeg would convert input.mp4 to output.mp4 using H.265 video codec and set the CRF quality setting to 28 and AAC audio at 128kbps for efficient compression suitable for web playback in <video>component. This reduces load time and improves performance across rendering modes(SSG,SSR, ISR).

  • Resolution and Adaptive Bitrate Streaming: Using 720p or lower balances quality and size for non-critical videos. Formats like HLS or MPEG-DASH deliver videos at varying bitrates based on the user's network.

ffmpeg -i input.mp4 -hls_time 10 -hls_list_size 0 output.m3u8

Implementing Lazy Loading in Next.js app

Deferring non-critical videos reduces initial page load time, especially for videos below the fold. This can be done by setting native preload=”none “ to prevent initial buffering, while preload=”metadata” would only load metadata, OR  by using the Intersection Observer API to load when the video enters the viewport.

import { useRef, useEffect } from 'react'; export function Video() {   const videoRef = useRef(null);   useEffect(() => {     const observer = new IntersectionObserver(       ([entry]) => {         if (entry.isIntersecting) {           videoRef.current.load();           observer.disconnect();         }       },       { rootMargin: '100px' }     );     observer.observe(videoRef.current);     return () => observer.disconnect();   }, []);   return (     <video       ref={videoRef}       className="w-full h-auto max-w-full aspect-video"       controls       preload="none"     >       <source src="/path/to/video.mp4" type="video/mp4" />
      <source src="/path/to/video.webm" type="video/webm" />       <track         src="/path/to/captions.vtt"         kind="subtitles"         srcLang="en"         label="English"       />       Your browser does not support the video tag.     </video>   ); }
  • Hosting videos on CDN: Content Delivery Networks(CDNs) like Cloudflare and Vercel reduce latency by serving videos close to the user. The external videos stored on your CDN can be referenced in your <video> component.

<video controls preload="metadata">  <source src="https://cdn.example.com/video.mp4" type="video/mp4" /></video>

To maximize CDN caching, configure cache headers for static videos.

E.g., setting Cache-Control: max-age=31536000 would instruct browsers and CDN to cache the resource for 1 year, effectively reducing requests to the server and improving load time in your Next.js application. 

One of the advantages of using a Headless CMS like BCMS is that you do not have to set up a CDN for storing or hosting, since it takes care of all your static assets.

A basic video widget would contain a file to upload the videos and a boolean to control playbacks. BCMS automatically creates a generated TypeScript type for you, which can be used to make a Next.js component that shows the video.

export interface VideoWidget {    file?: PropMediaDataParsed;    autoplay?: boolean;}
import React, { FC } from 'react'; import type { VideoWidgetType } from '@bcms-types/ts'; interface Props {   data: VideoWidgetType; } const VideoWidget: FC<Props> = ({ data }) => {   return (     <div className="my-6 w-full">       <video         controls={!data.autoplay}         autoPlay={data.autoplay}         playsInline={data.autoplay}         muted={data.autoplay}         loop={data.autoplay}         className="w-full aspect-video"         preload="none"       >         <source src={data.file.src} type="video/mp4" />         Your browser does not support the HTML5 Video element.       </video>     </div>   ); }; export default VideoWidget;
  • Using 3rd party libraries/services: Services such as Cloudinary,Mux and next-video will do the heavy-lifting for you by solving complex problems such as embedding, storing and streaming videos.

    This is particularly helpful when a headless CMS like BCMS gives you the liberty to host your videos wherever you want. Since BCMS leaves the video in the original format without compressing or transcoding, an external service like Cloudinary could be used, with the URL stored in the widget.

Optimize images for better performance in Next.js image

The built-in image optimization from next/image improves website performance by reducing image file size while maintaining the quality for faster load time and better user experience.

The <Image>component automatically compresses, resizes, and converts to modern formats like WebP that are supported by the browser for a smaller file size.

In the same way, the <BCMSImage> component also optimizes images for responsive sizing and caching that are stored on BCMS.

 <BCMSImage     media={card.cover_image}     clientConfig={bcmsConfig}     className="object-cover aspect-[1.25] w-full rounded-2xl overflow-hidden mb-4 lg:aspect-[1.77] lg:mb-6"   />

Code splitting:

Splitting your JavaScript code into smaller chunks is beneficial for your app’s performance as it does the following:

  • Faster Load Times: By loading only the essential code for the current page, code splitting reduces initial bundle size, making pages much more quickly. This improves First Contentful Paint (FCP) and Time to Interactive (TTI), especially for users on slow networks.

  • Better User Experience: Using <Suspense> with a fallback prevents blank screens, keeping users engaged.

  • Reduced Resource Usage: Smaller bundles mean less JavaScript for the browser to download and process, saving CPU and memory. This is crucial for low-end devices or mobile users.

  • Scalability: As an app grows with more pages or features, code splitting prevents the bundle from becoming bloated, maintaining performance even with a large codebase.

  • SEO compatibility: In Next.js, code splitting ensures SEO-critical content is rendered in HTML while deferring non-critical JavaScript.

  • Efficient Navigation: With route-based splitting (automatic in Next.js) and dynamic imports (via next/dynamic), only the code for the current route or component loads, speeding up page transitions.

How BCMS Benefits from Code Splitting?

  • Efficient Content Delivery: Code splitting ensures BCMS content is displayed quickly by reducing the JavaScript needed, while Suspense provides a fallback during loading.

  • SEO Support: Server-rendering BCMS content with ssr: true ensures it’s indexed, critical for CMS-driven sites.

  • Performance with Large Data: Splitting heavy components like MenuMeals handles large BCMS datasets efficiently, avoiding bloated bundles.

  • Scalability: As BCMS content grows, code splitting keeps the app performant by loading only the necessary code for each page.

import dynamic from 'next/dynamic';import { Suspense } from 'react';import {  FoodItemEntry,  FoodItemEntryMetaItem,  MealTypeEntry,  MealTypeEntryMetaItem,  MenuPageEntry,  MenuPageEntryMetaItem,} from '@bcms-types/types/ts';import { Metadata } from 'next';import { notFound } from 'next/navigation';import { bcmsPrivate } from '@/bcms-private';import { bcmsPublic } from '@/bcms-public';// Dynamically import MenuMealsconst MenuMeals = dynamic(() => import('@/components/menu-page/Meals'), {  ssr: true, // Server-render for SEO});export async function generateMetadata(): Promise<Metadata> {  const menuPageEntries = (await bcmsPrivate.entry.getBySlug(    'menu',    'menu-page',  )) as MenuPageEntry;  if (!menuPageEntries) {    return notFound();  }  const menuPageMeta = menuPageEntries.meta.en as MenuPageEntryMetaItem;  const pageTitle = `${menuPageMeta.seo?.title || menuPageMeta.title} - Tastyyy`;  return {    title: pageTitle,    openGraph: { title: pageTitle },    twitter: { title: pageTitle },  };}const MenuPage: React.FC = async () => {  const menuPageEntries = (await bcmsPrivate.entry.getBySlug(    'menu',    'menu-page',  )) as MenuPageEntry;  if (!menuPageEntries) {    return notFound();  }  const menuPageMeta = menuPageEntries.meta.en as MenuPageEntryMetaItem;  const mealTypes = (    (await bcmsPrivate.entry.getAll('meal-type')) as MealTypeEntry[]  ).map((e) => e.meta.en as MealTypeEntryMetaItem);  const foodItems = (    (await bcmsPrivate.entry.getAll('food-item')) as FoodItemEntry[]  ).map((e) => e.meta.en as FoodItemEntryMetaItem);  return (    <div>      <Suspense fallback={<div>Loading menu...</div>}>        <MenuMeals          meta={menuPageMeta}          meals={mealTypes}          foodItems={foodItems}          bcmsConfig={bcmsPublic.getConfig()}        />      </Suspense>    </div>  );};export default MenuPage;

CDN for digital teleportation:

 Content Delivery Network(CDN) is a geographically distributed network of servers that store cached copies of our site's content, such as images, videos, CSS files and JavaScript bundles and serve users closest to their location.

Although Next.js is designed for performance, delivering static assets and client-side bundles is crucial, and here is how CDN becomes indispensable:

  • Reduces latency when a user requests CDN since they get directly to the closest available PoP. 

  • CDN drastically reduces the time it takes to download all necessary files, including your JavaScript chunks, CSS, images, and video assets into your browser, leading to a smoother initial rendering of your application.

  • Reduces the load on your primary server. This approach allows Next.js servers to focus on dynamic requests like API calls and databases without performance degradation.

  • Many CDNs offer integrated security features like DDoS protection and SSL/TLS encryption that act as an additional layer of defense against malicious attacks targeted towards your application.

How does BCMS help with CDN?

Modern headless CMS like BCMS come with built-in CDN capabilities. This means that a separate CDN set up is not required for your assets – BCMS seamlessly delivers content closest to your users.

Trim the fat bundle size with next/bundle-analyzer:

With web applications growing in complexity, so is their bundled size. A large bundle sizer would usually mean it takes a longer loading time for users. The need to identify and eliminate unnecessary code is crucial for optimizing performance. 

The next/bundle-analyzer is an insightful package that visualizes the contents of your JavaScript bundles as a treemap. It shows you the modules included in your bundle, their individual size, and how they contribute to the overall bundle size.

Let’s say you have a restaurant website that has a prominent photo gallery that showcases your dishes and ambiance. You’ve used a feature-rich image carousel library like react-image-gallery and the bundle-analyzer shows a sizable chunk of your main bundle is consumed by this library, even when the gallery isn’t immediately visible to the user.

After this analysis, you decide to load the gallery component only when it’s needed using next/dynamic.

// components/DynamicImageGallery.js
import dynamic from 'next/dynamic'; const ImageGallery = dynamic(() => import('react-image-gallery'), {   ssr: false,   loading: () => <p>Loading gallery...</p>
});
export default ImageGallery;
// pages/index.js
import DynamicImageGallery from '../components/DynamicImageGallery'; function HomePage() {   // Define your image gallery items here   const images = [     {       original: 'https://picsum.photos/id/1018/1000/600/',       thumbnail: 'https://picsum.photos/id/1018/250/150/',       description: 'Delicious Pasta Dish',     },     {       original: 'https://picsum.photos/id/1015/1000/600/',       thumbnail: 'https://picsum.photos/id/1015/250/150/',       description: 'Freshly Baked Bread',     },     {       original: 'https://picsum.photos/id/1016/1000/600/',       thumbnail: 'https://picsum.photos/id/1016/250/150/',       description: 'Cozy Restaurant Interior',     },   ];   return (     <div>       <h1>Welcome to Our Restaurant!</h1>       <p>Experience the finest culinary delights in town.</p>       {/* Other content of your homepage */}       <section style={{ margin: '40px 0' }}>         <h2>Our Delicious Offerings</h2>         {/* The DynamicImageGallery component is rendered here */}         <DynamicImageGallery items={images} />       </section>       <p>Visit us soon!</p>     </div>   ); } export default HomePage;

Conclusion: Keep your NextJS app fast with built-in features. Lighthouse will love it. 

Much like perfecting a signature dish, the journey of optimization is never truly finished.

Since your Next.js application is bound to evolve, you might introduce new features or integrate a third-party service, which will only add to your codebase. Each addition could be an opportunity to refine your application or add more bloat to it.

As browsers evolve and users demand instantaneous access, what may feel fast now could be sluggish tomorrow. The goal is to stay abreast of the latest technologies and practices so you can continue to fine-tune your application.

It takes a minute to start using BCMS

Gradient

Join our Newsletter

Get all the latest BCMS updates, news and events.

You’re in!

The first mail will be in your inbox next Monday!
Until then, let’s connect on Discord as well:

Join BCMS community on Discord

By submitting this form you consent to us emailing you occasionally about our products and services. You can unsubscribe from emails at any time, and we will never pass your email to third parties.

Gradient