Next.js Concepts

Get a primer on some common concepts not directly related to the CMS that you'd come across when building frontend projects.

Note: For a comprehensive guide always refer to the Next.js Docs - We'll only be breezing over some concepts that have a lil something to do with concepts you might need when using a Headless CMS.

If you're new to Next.js and Headless CMS in general, our Next.js Starter Kit is an excellent place to start.

So, let's dip into the core concepts you'll come across in a super simple and digestible scope.

What's getStaticProps?

If you have content that doesn’t change too often—like simple marketing pages like an /about or a /contactgetStaticProps would be your go-to. It fetches data at build time, generating fully static HTML for your pages. This means your users get ⚡ page loads, and your server gets a well-deserved break without having to clock in to overtime everytime that page needs rendering.

For example, imagine you’re fetching a list of blog posts from an API:

export async function getStaticProps() {
const res = await fetch('https://site-api.datocms.com/posts');
const posts = await res.json();
return {
props: { posts },
};
}

This function runs during the build process, so the data is pre-rendered into HTML. The result? Static pages that load almost instantly. And if you combine this with ISR (more on that later), you can even update your static content periodically without redeploying the entire site.

getStaticProps is a dope for any site that values speed and SEO without sacrificing dynamic content.

What about getServerSideProps?

On the flipside, this one's all about real time data for every request.

If you need up-to-the-minute data for every request, getServerSideProps has your back. Unlike getStaticProps, this function runs on the server each time a page is requested, ensuring users see the most current content.

Take a dashboard as an example, and let's assume it's requesting user profile data that's always changing for whatever reason:

export async function getServerSideProps() {
const res = await fetch('https://site-api.datocms.com/user');
const user = await res.json();
return {
props: { user },
};
}

Here, the server fetches user-specific data for each request, allowing you to render content dynamically. It’s perfect for super frequently changing things like stock tickers, social feeds, dashboards, admin panels, or any app where content changes frequently.

The tradeoff? It’s slower than static generation, of course, since a server call happens on every request. But when real-time accuracy is non-negotiable, getServerSideProps lets you do what you gotta do.

Ok, now what's getStaticPaths?

TLDR? Predefining some dynamic routes.

Dynamic routes in Next.js—like /blog/[slug]—are super convenient, but how do you tell Next.js which pages to pre-render? That’s where getStaticPaths comes in. It works alongside getStaticProps to generate paths for dynamic pages during the build process.

Get it? You got props. They need paths.

Here’s how it works:

export async function getStaticPaths() {
const res = await fetch('https://site-api.datocms.com/posts');
const posts = await res.json();
return {
paths: posts.map((post) => ({ params: { slug: post.slug } })),
};
}

This function ensures that all pages defined in the paths array are pre-rendered into static HTML. getStaticPaths is essential for projects with dynamic content where you're handling things in the CMS for every new record to have a new slug on publish, without having to refactor or rebuild things (depending on your build), and is relevant from simple blogs to packed e-commerce catalogs.

What's this App Router everyone's talking about?

Ever used Next.js in the past and remember the simpler pages/ times? Well, since Next.js 13, the App Router came about, and these days app/ is favored over src/.

Next.js 13 introduced the App Router to boost how routing works by bringing powerful features like nested layouts, server components, and parallel routes.

With the App Router, you can now define shared layouts that automatically wrap all nested pages:

app/
├── layout.js // App-wide layout
├── page.js
├── dashboard/
│ ├── layout.js // Nested layout for just the dashboard
│ ├── page.js
│ └── settings/
│ └── page.js

This approach simplifies complex apps by letting you define reusable layouts and isolate functionality. Add features like server-side rendering and static generation, and you’ve got a modern routing system designed for scale.

Now this is an ULTRA simplification, it's really worth diving deep into their docs to learn how the app router works.

Next.js Server Components

Server Components are a realllly fun for working with React. Instead of shipping everything to the browser, they allow components to render on the server, sending only the final HTML to the client. This keeps JavaScript bundles small and pages crazy fast depending on the way they're set up.

For example, let’s say you have a product list that requires server-side fetching:

export default async function ProductList() {
const products = await fetch('https://site-api.datocms.com/products/pink-boots').then((res) => res.json());
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
);
}

With server components, this logic runs entirely on the server, meaning no extra JavaScript gets sent to the browser. This approach is perfect for content-heavy apps like eCom shopfronts where performance matters.

Next.js Middleware

From a marketing standpoint this is actually really cool. Next's middleware allows for all sorts of fun on the edge, like AB testing and redirects, without needing full-on server functions. Next.js Middleware lets you execute logic before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.

For example, if your eCom shop needs to have geo redirects to serve different content to users in different locations, you'd have a setup around these lines:

import { NextResponse } from 'next/server';
export async function middleware(req) {
const country = req.geo?.country || 'IT';
if (country === 'DE') {
return NextResponse.redirect(new URL('/de', req.url));
}
}

Here, even though the default location/site is Italian, users sending a request from Germany are redirected to the German locale all before any other requests are completed.

And what's this Edge Runtime?

The Edge Runtime in Next.js enables you to run server-side logic closer to your users. How close? ON THE EDGE CLOSE! Jokes aside, it'll run server side logic from the closest CDN edge node to the user. This means faster response times for often-heavy tasks like A/B testing, user authentication, or geo-based personalization.

For example, you can combine the Edge Runtime with Middleware for instant redirects:

export const config = { runtime: 'edge' };
export default async function handler(req) {
const country = req.geo?.country || 'DE';
return new Response(`Hallochen!`);
}

Edge Runtime is lightweight, fast, and designed for modern web needs, making it really sweet to work with.

What is ISR?

ISR or Incremental Static Regeneration was one of Next.js's dopest features for "mostly" Static Sites that needed periodic updates without redeploying the whole damn site.

What if you want the speed of static pages but the flexibility of dynamic updates? ISR allows you to update static content at specific intervals or on demand—no need to redeploy your site. So if your entire site is Static but you post blog posts every now and then, ISR is what would let your site update every x hours without you needing to deploy everything.

export async function getStaticProps() {
const res = await fetch('https://site-api.datocms.com/posts');
const posts = await res.json();
return {
props: { posts },
revalidate: 3600,
};
}

For example, if you got a blog post, a little snippet like that will regenerate all your blog posts (only) every 1 hour, letting your site refresh when there's new content but leaving the rest of it untouched.

PS: while ISR is cool, check out our Cache Tags which allow you to simply tag webpages with unique identifiers, so when the content from the CMS is updated, these tags can trigger an immediate and precise cache invalidation only for the pages that actually include that content, and need to be regenerated.

What's next/image?

Images can make or break your site’s performance if you're not optimizing them well. That’s why Next.js’s next/image is such a sweet feature in Next. It handles stuff like resizing, lazy loading, and modern formats like WebP—all automatically and out of the box. You don’t need third-party DAMs or anything of the sort.

Simply slap some params to your images when dropping them in:

import Image from 'next/image';
<Image
src="/shawarma.jpg"
alt="Shawarma"
width={800}
height={600}
...
/>

It even supports dynamic imports for CMS-driven content in case you're not using Dato's inbuilt optimizations for whatever reason 😛.

What's next/seo?

Another cool feature out of the box for making websites easier to build. Most Headless CMS (yours truly very much excluded 💁‍♀️) don't offer strong SEO-focused features, in which case next/seo is a really fun solution.

We all know that SEO is vital for visibility (I mean, that's the reason behind this whole Academy), but managing metadata and OG tags manually is a f*n chooooore. Enter next/seo, a package designed to simplify SEO in Next.js. It helps you manage titles, descriptions, canonical URLs, and even social media metadata without the boilerplate.

The implementation is super straight forward too, if you're not just relying on site-wide fallbacks in your config, then you can add in metadata per page via the <NextSEO /> component

import { NextSeo } from 'next-seo';
<NextSeo
title="Best Shawarmas in Berlin"
description="If the toum no gucci, the shawarma no gucci."
canonical="https://berlin-shawarmas.com"
openGraph={{
url: 'https://berlin-shawarmas.com',
title: 'Best Shawarmas in Berlin',
description: 'If the toum no gucci, the shawarma no gucci.',
}}
/>

What is next/i18n?

Another cool feature for international sites, especially when your CMS doesn't have strong localization built in (don't be looking at us, we got this 💅).

Building multilingual websites used to be (and still can be) a pain, but Next.js has simplified the process with its built-in i18n support. This feature handles locale detection, routing, and translations seamlessly.

module.exports = {
i18n: {
locales: ['en', 'it', 'de'],
defaultLocale: 'it',
},
};

Next.js automatically routes users to the correct locale, like /fr for French or /de for German, provided you have them set up. You can even integrate it with libraries like next-translate for more advanced use cases. Whether you’re building a global e-commerce site or a multilingual blog, next/i18n makes localization feel effortless.

Remember the example of DE from when we were talking about middleware? Yeah? i18n + middleware is such a sweet, sweet combo :chef-kiss:.

What's next/script?

We ain't calling out anyone specific, but so many sites see reallllllly see terrible performance when loading 50 shades of 3rd party scripts for things like analytics and tracking and personalization and ads and heaven knows what else.

So, Next.js has next/script, to let you load scripts intelligently. Will they fix terrible-to-begin-with-scripts that are heavy? No. But they will let you load them using smarter strategies like lazyOnload or beforeInteractive to salvage whatever performance gains you can without dropping the script.

For example:

import Script from 'next/script';
<Script
src="https://example.com/script.js"
strategy="lazyOnload"
/>

will ensure that scripts only load when they're needed.

Previous chapter
Start using DatoCMS today
According to Gartner 89% of companies plan to compete primarily on the basis of customer experience this year. Don't get caught unprepared.
  • No credit card
  • Easy setup
Subscribe to our newsletter! 📥
One update per month. All the latest news and sneak peeks directly in your inbox.
support@datocms.com ©2025 Dato srl, all rights reserved P.IVA 06969620480