Asttero

Data Fetching in Next.js — A Beginner's Guide

Data fetching in Next.js — a beginner's guide

The traditional approach to building online stores often involves trade-offs between flexibility and speed. Modern e-commerce, however, requires technology that eliminates delays and enables instant offer rendering. Data fetching in Next.js — especially since the App Router architecture was introduced — redefines how we retrieve and present data to users. Understanding these mechanisms is the first step toward building an efficient headless store. We explain how to fetch data in Next.js effectively and securely, combining theory with practical e-commerce examples.

Data fetching in Next.js — a beginner's guide

A new era of data fetching: Why the Next.js App Router changes the rules

The introduction of the App Router in Next.js brought a fundamental change in web application architecture. The most important innovation is that all components inside the app directory are treated as Server Components by default. That means their code runs directly on the server, and the user's browser receives ready-rendered HTML plus a minimal amount of JavaScript. Data fetching in Next.js gained an entirely new dimension with the spread of Server Components.

For online store performance, this has enormous significance. Traditional React applications force the browser to download large code bundles, run scripts, and only then send API requests for product data or prices. In Next.js, this process moves to the server. Data is fetched close to the database or e-commerce platform API, drastically shortening the wait time before the offer appears.

When designing a modern store, it helps to distinguish tasks performed on the user side from those handled on the server from the start. The split between Server and Client Components in Next.js helps optimize resource usage. This evolution began with what's new in Next.js 13, when the new directory system was introduced and the previous Pages Router was phased out. Developers gained a powerful tool for optimizing the purchase path.

Server-side data fetching (Server Components) in practice

Server-level data fetching in Next.js eliminates the need for complex loading states and hooks that were standard in classic React. Code becomes simpler, more readable, and easier to maintain.

What does a basic server fetch query look like?

In the App Router, a server component can simply be an async function. That allows using async and await directly in the component body. Next.js extends the browser's native fetch API, so you do not need additional libraries to manage server-side requests.

Here is an example of a simple component fetching a product list from an external API:


async function getProducts() {

const res = await fetch('https://api.example.com/products');

if (!res.ok) {

throw new Error('Failed to fetch data');

}

return res.json();

}

export default async function ProductsPage() {

const products = await getProducts();

return (

<div>

<h1>Our Products</h1>

<ul>

{products.map((product) => (

<li key={product.id}>{product.name}</li>

))}

</ul>

</div>

);

}

API key security and the server-only package

When integrating a store with external systems such as the Shopify Storefront API, you must use private access tokens. In traditional client applications, there was a risk these keys would leak to the user's browser. Server-side data fetching in Next.js completely solves this problem.

To guarantee security, store tokens in environment variables without the NEXT_PUBLIC prefix. Variables without this prefix are unavailable to client code. It is also worth protecting code against accidentally importing server modules into client components. The server-only package serves this purpose. After installing it and adding the import at the top of the file, any attempt to use this code in the browser will fail at build time:


import 'server-only';

export async function fetchShopifyData(query, variables) {

const res = await fetch('https://your-store.myshopify.com/api/2024-04/graphql.json', {

method: 'POST',

headers: {

'Content-Type': 'application/json',

'X-Shopify-Storefront-Access-Token': process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN,

},

body: JSON.stringify({ query, variables }),

});

return res.json();

}

This architecture enables secure building of advanced integrations and custom Shopify apps that process sensitive data without risk of exposure on the frontend.

A caching revolution: Differences between Next.js 14 and Next.js 15

Cache management is a key element of optimizing any online store. Serving static product pages quickly while keeping prices and inventory up to date requires precise configuration. In this area, a very important change occurred between Next.js versions 14 and 15.

In Next.js 13 and 14, fetch queries were cached by default. That meant the framework automatically stored API responses and served them as static assets (force-cache behavior). Although that sped up the store, it often led to stale data if the developer forgot to configure revalidation.

In Next.js 15, default behavior changed. Fetch queries are no longer cached by default (no-store behavior). The framework now behaves like standard browser behavior. If you want data cached, you must configure it explicitly in the query options.

Two approaches manage data freshness:

Example of cache and time-based revalidation configuration in Next.js 15:


// Data will be cached for up to 3600 seconds (1 hour)

const res = await fetch('https://api.example.com/products', {

next: { revalidate: 3600 }

});

When to go client-side? Browser data fetching

Despite the huge advantages of server rendering, data fetching in Next.js can also happen directly in the user's browser. This applies to all interactive elements that depend on immediate customer actions or require real-time personalization.

Client components must be marked with the "use client" directive at the very top of the file. Typical scenarios where browser data fetching is essential include:

For browser data fetching, we do not recommend the raw useEffect hook because it is easy to introduce memory leaks or unnecessary re-renders. Instead, use dedicated libraries such as SWR (created by the Next.js team) or TanStack Query. They offer automatic caching, retry of failed requests, and fast UI updates.

Smooth page loading: Handling loading and error states

E-commerce users do not like waiting for the offer to load. If the process takes too long, they simply leave the store. Next.js offers built-in mechanisms for smooth async management that improve perceived speed.

Using these solutions has a major impact on routing structure and organization in Next.js, where each path segment can independently manage its loading and error states.

Top-tier performance: How to eliminate the waterfall effect

When designing API queries, it is easy to fall into sequential data fetching known as the waterfall effect. This occurs when one query waits for the previous one to finish even though the data is not directly dependent.

Imagine a product page where you need to display item details, customer reviews, and a list of related products. If you fetch this data sequentially:


// BAD PATTERN — queries block each other

const product = await getProduct(id);

const reviews = await getReviews(id); // waits for getProduct

const related = await getRelatedProducts(id); // waits for getReviews

Offer load time will be the sum of all three query times. To prevent that, initiate queries in parallel. In Next.js, the simplest solution is Promise.all:


// GOOD PATTERN — queries run in parallel

const productPromise = getProduct(id);

const reviewsPromise = getReviews(id);

const relatedPromise = getRelatedProducts(id);

const [product, reviews, related] = await Promise.all([

productPromise,

reviewsPromise,

relatedPromise

]);

The browser receives the complete dataset much faster, which directly improves performance metrics and shopping comfort.

Next.js and Shopify: The foundation of a fast headless store

Choosing the right technical architecture has a direct impact on online business profitability. Store load speed and smooth movement through checkout steps are factors that directly shape conversion rate (CVR) and average order value (AOV).

Combining Next.js flexibility with Shopify reliability in a headless model lets you build a store free of traditional theme limitations. Proper data fetching in Next.js and optimized queries to the Shopify Storefront API ensure the store stays stable even during sudden sales peaks such as Black Friday.

Taking this step gives you full control over offer presentation and site speed. If you want to learn more about the benefits of this approach, it is worth exploring headless e-commerce benefits. As an official Shopify partner, we support brands in moving to modern technologies. If you are planning to grow your business and are interested in professional Shopify store implementation in a headless architecture, contact us to discuss project details.

FAQ

Do I need external libraries for server-side data fetching in the Next.js App Router?

No, it is not necessary. Next.js extends the native fetch API with advanced caching and revalidation options, making standard queries fully sufficient and efficient in Server Components.

How do I protect Shopify Storefront API tokens from leaking to the browser?

Store tokens in environment variables without the NEXT_PUBLIC prefix. Additionally, use the 'server-only' package, which prevents accidentally running data-fetching code in the browser.

Why are my fetch queries in Next.js 15 not cached?

In Next.js 15, default caching behavior changed from force-cache to no-store. For data to be cached, explicitly pass the appropriate parameters in fetch configuration options, e.g. specifying revalidation time.

How does on-demand revalidation differ from time-based revalidation?

Time-based revalidation automatically refreshes data after a specified number of seconds. On-demand revalidation allows immediate cache clearing via a tag or path, for example after updating a product price in the Shopify admin panel.

Can I fetch data in the browser in Next.js?

Yes, that is recommended for interactive elements such as filters or the cart. It requires marking the component with the 'use client' directive and using libraries such as SWR or TanStack Query.

What is the waterfall effect and how do I avoid it?

The waterfall effect is sequential data fetching where one query waits for the previous one to finish. You can avoid it by initiating independent queries in parallel with Promise.all.

Bibliography