Skip to content

How to Choose Between Data Fetching Options in Next.js

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

A few days ago, we published another blog post related to Next.js: Understanding Next.js Data Fetching for Beginners.

In that blog post, we show the functions this framework provides us to perform different data fetching options. I hope you already have a basic understanding of Next.js. If not, I highly recommend that you first read the article linked above.

Let's get started.

There are multiple strategies for us to choose from, and it gets confusing at first to apply the correct solution in the correct place. We will take a real-life application, and understand which strategy works best for which scenarios.

The special functions that are used for pre-rendering in Next.js are:

  • getStaticProps
  • getStaticPaths
  • getServerSideProps

We will see in which scenarios you can apply these functions, and learn the best practices for doing so.

We are talking about an Online Store

Let's assume that we want to build an Online Store to display some products, a product page (and details), and an accounts page (order details, personal info, etc). Let's also assume that we don't want to use React because we want to optimize SEO.

Let's start thinking about this.

Landing Page (getStaticProps)

The landing of our OnlineStore is where you can see two things, the list of some products you want to highlight could be:

  • Featured Products
  • Popular products
  • Discounted Products
  • Upcoming Products

Whatever the case, the main thing is you already know which API you will be using, and probably that API will not change in the short term. In these scenarios, only getStaticProps is enough.

As we see in our previous post:

export async function getStaticProps() {
    const url = 'https://our-api-products.com/';
    const res = await fetch(url, {
        headers: {
        Accept: 'application/json',
        },
    });
    const data = await res.json();

    return {
        props: {
            products: data.products,
        },
    };
}

Now, our landing page will be pre-generated ahead of time and won't change. For further details and a breakdown of the getStaticProps method let's check our previous post getStaticProps.

Landing Page, but dynamic (getStaticProps + revalidate).

What happens if the landing page you are building is frequently changing the products based on user preferences? Then, you don't want to show the same page all the time.

In that case, you need to make sure that the generated pages will be updated at a regular interval.

With that in mind, you have to use the revalidate property. This property is defined in seconds to indicate the refresh interval.

export async function getStaticProps() {
    const url = 'https://our-api-products.com/';
    const res = await fetch(url, {
        headers: {
        Accept: 'application/json',
        },
    });
    const data = await res.json();

    return {
        props: {
            products: data.products,
        },
        revalidate: 20
    };
}

We gave our revalidate property the value of 20. It means we are still pre-generating our landing page, but it will be updated every 10 seconds. As we see in our previous post, this is called Incremental Static Regeneration.

Product detail (getStaticPaths)

Now, if for example, our customers click on one product, we have to show a details page of the product. We're calling a PI, like the product/?id=678.

What is interesting here, is we don't know which id we are looking for. So our server needs to know all of the valid ids ahead of time.

This is where getStaticPaths comes in. It tells the server all the available routes that we need to pre-generate.

export async function getStaticPaths() {
    const idList = getproducts().map(product => product.id)
    const params = idList.map(id => ({params: {productId: id}}))

    return {
        fallback: false,
        paths: params,
    };
}

export async function getStaticProps(context: GetStaticPropsContext) {
    const {params: { productId }} = context
    const productDetails = await getProductDetails(productId);

    return {
        props: { productDetails, }
    };
}

Now, only the pages with a valid id will be pre-generated.

Accounts Details (getServerSideProps)

Now, what if our customer wants to see her last purchases, or change her address or other personal info? After that, we need to validate some things.

Or, we want to make sure that the user has the right permissions to view the page. In these scenarios, we use getServerSideProps.

If you remember, in our previous post we talk about getServerSideProps, this option will not pre-generate any page. Instead, it will run the code inside the method every time the page is requested. Therefore this is more secure, but unfortunately less efficient.

export async function getServerSideProps() {
  const url = 'https://our-api-products.com/';
  const res = await fetch(url, {
    headers: {
      Accept: 'application/json',
    },
  });
  const data = await res.json();

  return {
    props: {
      products: data.products,
    },
  };
}

Conclusion

That's it. First, we saw different data-fetching options. Now, we see how to choose between them, in real-life scenarios probably we are facing every day.

Now you should have a little understanding of how we can use NextJS for improved performance and SEO-Friendly in your application.

So far, we learned:

  • If we had a page in which the content will be the same and will remain the same for a long time, it's better to use getStaticProps.

  • If we need a page whose content will be dynamic based on some conditions, it's better to use getStaticProps with a revalidating option.

  • On the other hand, if we want to build a page and be sure that the id items are all valid, it's more likely to use getStaticPaths.

  • But, if we need to make a secure but dynamic page, we will definitely need getServerSideProps.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Keeping Costs in Check When Hosting Next.js on Vercel cover image

Keeping Costs in Check When Hosting Next.js on Vercel

Vercel is usually the go-to platform for hosting Next.js apps, and not without reason. Not only are they one of the sponsors of Next.js, but their platform is very straightforward to use, not just for Next.js but for other frameworks, too. So it's no wonder people choose it more and more over other providers. Vercel, however, is a serverless platform, which means there are a few things you need to be aware of to keep your costs predictable and under control. This blog post covers the most important aspects of hosting a Next.js app on Vercel. Vercel's Pricing Structure Vercel's pricing structure is based on fixed and usage-based pricing, which is implemented through two big components of Vercel: the Developer Experience Platform (DX Platform) and the Managed Infrastructure. The DX Platform offers a monthly-billed suite of tools and services focused on building, deploying, and optimizing web apps. Think of it as the developer-focused interface on Vercel, which assists you in managing your app and provides team collaboration tools, deployment infrastructure, security, and administrative services. Additionally, it includes Vercel support. Because the DX Platform is developer-focused, it's also charged per seat on a monthly basis. The more developers have access to the DX Platform, the more you're charged. In addition to charging per seat, there are also optional, fixed charges for non-included, extra features. Observability Plus is one such example feature. The Managed Infrastructure, on the other hand, is what makes your app run and scale. It is a serverless platform priced per usage. Thanks to this infrastructure, you don't need to worry about provisioning, maintaining, or patching your servers. Everything is executed through serverless functions, which can scale up and down as needed. Although this sounds great, this is also one of the reasons many developers hesitate to adopt serverless; it may have unpredictable costs. One day, your site sees minimal traffic, and the next, it goes viral on Hacker News, leading to a sudden spike in costs. Vercel addresses this uncertainty by including a set amount of free serverless usage in each of its DX Platform plans. Once you exceed those limits, additional charges apply based on usage. Pricing Plans The DX Platform can be used in three different pricing plans on a team level. A team can represent a single person, a team within a company, or even a whole company. When creating a team on Vercel, this team can have one or more projects. The first of the three pricing plans is the Hobby plan, which is ideal for personal projects and experimentation. The Hobby plan is free and comes with some of the features and resources of the DX Platform and Managed Infrastructure out of the box, making it suitable for hosting small websites. However, note that the Hobby plan is limited to non-commercial, personal use only. The Pro plan is the most recommended for most teams and can be used for commercial purposes. At the time of this writing, it costs $20 per team member and comes with generous resources that support most teams. The third tier is the Enterprise plan, which is the most advanced and expensive option. This plan becomes necessary when your application requires specific compliance or performance features, such as isolated build infrastructure, Single Sign-On (SSO) for corporate user management or custom support with Service-Level Agreements. The Enterprise plan has a custom pricing model and is negotiated directly with Vercel. What Contributes to Usage Costs and Limiting Them Now that you've selected a plan for your team, you're ready to deploy Next.js. But how do you determine what contributes to your per-usage costs and ensure they stay within your plan limits? The Vercel pricing page has a detailed breakdown of the resource usage limits for each plan, which can help you understand what affects your costs. However, in this section, we've highlighted some of the most impactful factors on pricing that we've seen on many of our client projects. Number of Function Invocations Each time a serverless function runs, it counts as an invocation. Most of the processing on Vercel for your Next.js apps happens through serverless functions. Some might think that only API endpoints or server actions count as serverless function invocations, but this is not true. Every request that comes to the backend goes through a serverless function invocation, which includes: - Invoking server actions (server functions) - Invoking API routes (from the frontend, another system, or even within another serverless function) - Rendering a React server component tree (as part of a request to display a page) To give you an idea of the number of invocations included in a plan, the Pro plan includes 1 million invocations per month for free. After that, it costs $0.60 per million, which can total a significant amount for popular websites. To minimize serverless function invocations, focus on reducing any of the above points. For example: - Batch up server actions: If you have multiple server actions, such as downloading a file and increasing its download count, you can combine them into one server action. - Minimize calls to the backend: Closely related to the previous point, unoptimized client components can call the backend more than they need to, contributing to increased function invocation count. If needed, consider using a library such as useSwr or TanStack Query to keep your backend calls under control. - Use API routes correctly: Next.js recommends using API routes for external systems invoking your app. For instance, Contentful can invoke a blog post through a webhook without incurring additional invocations. However, avoid invoking API routes from server component tree renders, as this counts as at least two invocations. Reducing React server component renders is also possible. Not all pages need to be dynamic - convert dynamic routes to static content when you don’t expect them to change in real-time. On the client, utilize Next.js navigation primitives to use the client-side router cache. Middleware in Next.js runs before every request. Although this doesn't necessarily count as a function invocation (for edge middleware, this is counted in a separate bucket), it's a good idea to minimize the number of times it has to run. To minimize middleware invocations, limit them only to requests that require it, such as protected routes. For static asset requests, you can skip middleware altogether using matchers. For example, the matcher configuration below would prevent invoking the middleware for most static assets: ` Function Execution Time The duration your serverless function takes to execute counts as the execution time, and it impacts your end bill unless it's within the limits of your plan. This means that any inefficient code that takes longer to execute directly adds up to the total function invocation time. Many things can contribute to this, but one common pattern we've seen is not utilizing caching properly or under-caching. Next.js offers several caching techniques you can use, such as: - Using a data cache to prevent unnecessary database calls or API calls - Using memoization to prevent too many API or database calls in the same rendering pass Another reason, especially now in the age of AI APIs, is having a function run too long due to AI processing. In this case, what we could do is utilize some sort of queuing for long-processing jobs, or enable Fluid Compute, a recent feature by Vercel that optimizes function invocations and reusability. Bandwidth Usage The volume of data transferred between users and Vercel, including JavaScript bundles, RSC payload, API responses, and assets, directly contributes to bandwidth usage. In the Pro plan, you receive 1 TB/month of included bandwidth, which may seem substantial but can quickly be consumed by large Next.js apps with: - Large JavaScript bundles - Many images - Large API JSON payloads Image optimization is crucial for reducing bandwidth usage, as images are typically large assets. By implementing image optimization, you can significantly reduce the amount of data transferred. To further optimize your bandwidth usage, focus on using the Link component efficiently. This component performs automatic prefetch of content, which can be beneficial for frequently accessed pages. However, you may want to disable this feature for infrequently accessed pages. The Link component also plays a role in reducing bandwidth usage, as it aids in client-side navigation. When a page is cached client-side, no request is made when the user navigates to it, resulting in reduced bandwidth usage. Additionally, API and RSC payload responses count towards bandwidth usage. To minimize this impact, always return only the minimum amount of data necessary to the end user. Image Transformations Every time Vercel transforms an image from an unoptimized image, this counts as an image transformation. After transformation, every time an optimized image is written to Vercel's CDN network and then read by the user's browser, this counts as an image cache read and an image cache write, respectively. The Pro plan includes 10k transformations per month, 600k CDN cache reads, and 200k CDN cache writes. Given the high volume of image requests in many apps, it's worth checking if the associated costs can be reduced. Firstly, not every image needs to be transformed. Certain types of images, such as logos and icons, small UI elements (e.g., button graphics), vector graphics, and other pre-optimized images you may have optimized yourself already, don't require transformation. You can store these images in the public folder and use the unoptimized property with the Image component to mark them as non-transformable. Another approach is to utilize an external image provider like Cloudinary or AWS CloudFront, which may have already optimized the images. In this case, you can use a custom image loader to take advantage of their optimizations and avoid Vercel's image transformations. Finally, Next.js provides several configuration options to fine-tune image transformation: - images.minimumCacheTTL: Controls the cache duration, reducing the need for rewritten images. - images.formats: Allows you to limit eligible image formats for transformation. - images.remotePatterns: Defines external sources for image transformation, giving you more control over what's optimized. - images.quality: Enables you to set the image quality for transformed images, potentially reducing bandwidth usage. Monitoring The "Usage" tab on the team page in Vercel provides a clear view of your team's resource usage. It includes information such as function invocation counts, function durations, and fast origin transfer amounts. You can easily see how far you are from reaching your team's limit, and if you're approaching it, you'll see the amount. This page is a great way to monitor regularity. However, you don't need to check it constantly. Vercel offers various aspects of spending management, and you can set alert thresholds to get notified when you're close to or exceed your limit. This helps you proactively manage your spending and avoid unexpected charges. One good feature of Vercel is its ability to pause projects when your spending reaches a certain point, acting as an "emergency break" in the case of a DDoS attack or a very unusual spike in traffic. However, this will stop the production deployment, and the users will not be able to use your site, but at least you won't be charged for any extra usage. This option is enabled by default. Conclusion Hosting a Next.js app on Vercel offers a great developer experience, but it's also important to consider how this contributes to your end bill and keep it under control. Hopefully, this blog post will clear up some of the confusion around pricing and how to plan, optimize, and monitor your costs. We hope you enjoyed this blog post. Be sure to check out our other blog posts on Next.js for more in-depth coverage of different features of this framework....

Internationalization in Next.js with next-intl cover image

Internationalization in Next.js with next-intl

Internationalization in Next.js with next-intl Internationalization (i18n) is essential for providing a multi-language experience for global applications. next-intl integrates well with Next.js’ App Router, handling i18n routing, locale detection, and dynamic configuration. This guide will walk you through setting up i18n in Next.js using next-intl for URL-based routing, user-specific settings, and domain-based locale routing. Getting Started First, create a Next.js app with the App Router and install next-intl: ` Next, configure next-intl in the next.config.ts file to provide a request-specific i18n configuration for Server Components: ` Without i18n Routing Setting up an app without i18n routing integration can be advantageous in scenarios where you want to provide a locale to next-intl based on user-specific settings or when your app supports only a single language. This approach offers the simplest way to begin using next-intl, as it requires no changes to your app’s structure, making it an ideal choice for straightforward implementations. ` Here’s a quick explanation of each file's role: * translations/: Stores different translations per language (e.g., en.json for English, es.json for Spanish). Organize this as needed, e.g., translations/en/common.json. * request.ts: Manages locale-based configuration scoped to each request. Setup request.ts for Request-Specific Configuration Since we will be using features from next-intl in Server Components, we need to add the following configuration in i18n/request.ts: ` Here, we define a static locale and use that to determine which translation file to import. The imported JSON data is stored in the message variable, and is returned together with the locale so that we can access them from various components in the application. Using Translation in RootLayout Inside RootLayout, we use getLocale() to retrieve the static locale and set the document language for SEO and pass translations to NextIntlClientProvider: ` Note that NextIntlClientProvider automatically inherits configuration from i18n/request.ts here, but messages must be explicitly passed. Now you can use translations and other functionality from next-intl in your components: ` In case of async components, you can use the awaitable getTranslations function instead: ` And with that, you have i18n configured and working on your application! \ Now, let’s take it a step further by introducing routing. \ With i18n Routing To set up i18n routing, we need a file structure that separates each language configuration and translation file. Below is the recommended structure: ` We updated the earlier structure to include some files that we require for routing: * routing.ts: Sets up locales, default language, and routing, shared between middleware and navigation. * middleware.ts: Handles URL rewrites and locale negotiation. * app/[locale]/: Creates dynamic routes for each locale like /en/about and /es/about. Define Routing Configuration in i18n/routing.ts The routing.ts file configures supported locales and the default locale, which is referenced by middleware.ts and other navigation functions: ` This configuration lets Next.js handle URL paths like /about, with locale management managed by next-intl. Update request.ts for Request-Specific Configuration We need to update the getRequestConfig function from the above implementation in i18n/request.ts. ` Here, request.ts ensures that each request loads the correct translation files based on the user’s locale or falls back to the default. Setup Middleware for Locale Matching The middleware.ts file matches the locale based on the request: ` Middleware handles locale matches and redirects to localized paths like /en or /es. Updating the RootLayout file Inside RootLayout, we use the locale from params (matched by middleware) instead of calling getLocale() ` The locale we get from the params was matched in the middleware.ts file and we use that here to set the document language for SEO purposes. Additionally, we used this file to pass configuration from i18n/request.ts to Client Components through NextIntlClientProvider. Note: When using the above setup with i18n routing, next-intl will currently opt into dynamic rendering when APIs like useTranslations are used in Server Components. next-intl provides a temporary API that can be used to enable static rendering. Static Rendering for i18n Routes For apps with dynamic routes, use generateStaticParams to pass all possible locale values, allowing Next.js to render at build time: ` next-intl provides an API setRequestLocale that can be used to distribute the locale that is received via params in layouts and pages for usage in all Server Components that are rendered as part of the request. You need to call this function in every layout/page that you intend to enable static rendering for since Next.js can render layouts and pages independently. ` Note: Call setRequestLocale before invoking useTranslations or getMessages or any next-intl functions. Domain Routing For domain-specific locale support, use the domains setting to map domains to locales, such as us.example.com/en or ca.example.com/fr. ` This setup allows you to serve localized content based on domains. Read more on domain routing here. Conclusion Setting up internationalization in Next.js with next-intl provides a modular way to handle URL-based routing, user-defined locales, and domain-specific configurations. Whether you need URL-based routing or a straightforward single-locale setup, next-intl adapts to fit diverse i18n needs. With these tools, your app will be ready to deliver a seamless multi-language experience to users worldwide....

Sharing Signals and Stores: Context API in SolidJS cover image

Sharing Signals and Stores: Context API in SolidJS

Introduction Welcome to our latest blog post on SolidJS, where we delve into the world of the Context API. Context API is a popular and versatile feature in SolidJS, allowing developers to pass data and functions through the component tree without the need for props drilling. In this post, we will discuss how SolidJS implements the Context API, including the creation of contexts, sharing Signals and stores, and utilizing them within the components. Whether you are a seasoned developer, or just starting out, this post will provide valuable insights into the use of Context API in SolidJS and what are the advantages in the Reactivity ecosystem. So sit back, grab a cup of coffee, and let's dive in! What is SolidJS? SolidJS is a unique and powerful JavaScript framework that is quickly gaining popularity among developers. One of the key features that sets SolidJS apart from other frameworks is its reactive nature. How about reactivity? Reactivity is a programming paradigm through which the application automatically updates and re-renders when the data changes. This means that developers do not have to manually update the view when the data changes, saving a lot of time and effort. SolidJS achieves this reactivity by using a virtual DOM which is a lightweight representation of the actual DOM. This virtual DOM is then used to update the actual DOM, making the process of updating the view much faster and more efficient. Performance Another advantage of SolidJS is its performance. SolidJS is designed to be fast and efficient thanks to its use of a virtual DOM and a functional programming approach. By using functional components and immutability, SolidJS is able to optimize the process of re-rendering, resulting in a smoother and more responsive user experience. In addition to its reactivity and performance, SolidJS also has a small footprint and a simple API. The framework is lightweight and easy to learn, making it a great choice for developers of all skill levels. Signals: SolidJS Signals are a powerful feature that allow for easy communication between different parts of your application. They are similar to events or hooks in other frameworks, but with a few key differences. One of the key advantages of Signals in SolidJS is that they are fully reactive, meaning that they automatically update and re-render when the data changes. This makes it easy to create responsive and dynamic applications without having to manually update the view. To use Signals in SolidJS, you need to first create a signal by using the createSignal function. ` Store In SolidJS, a store works similar to other frontend frameworks. To keep things light, SolidJS creates underlying Signals only for properties that are accessed under tracking scopes. All Signals in Stores are created lazily as requested. The createStore call takes the initial value, and returns a read/write tuple similar to Signals. The first element is the store proxy which is readonly, and the second is a setter function. What About context? SolidJS provides a context API to pass data around without relying on passing props; this is useful in sharing Signals and Stores as described above. According to SolidJS docs: “Using Context has the benefit of being created as part of the reactive system and managed by it.” Getting Started: First, let’s create our context: ` Then we can consume our recently Context created: ` To use context, we'll first wrap our App component in order to provide it globally: ` We can now consume the context and our components will look like this: ` ` Conclusion In this article, we were able to use the contextAPI to share the props we want across the app, and benefit from the performance of Reactivity because of that. This will make our application more readable, but also brings some additional benefits to the app which are as follows: - avoid passing props down through multiple levels of the component tree. - reducing the number of re-renders that occur when props are passed down. - by isolating state and props to a specific context, you can make your code more organized and easier to understand. - components that use the Context API are more easily tested. Would you like to learn more about Signals and Stores, or SolidJS in general? You can see in detail how this was maximized in one of This Dot Labs' open source project starter.dev GitHub Showcases....

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers cover image

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers

Before she was a software developer at freeCodeCamp, Jessica Wilkins was a classically trained clarinetist performing across the country. Her days were filled with rehearsals, concerts, and teaching, and she hadn’t considered a tech career until the world changed in 2020. > “When the pandemic hit, most of my gigs were canceled,” she says. “I suddenly had time on my hands and an idea for a site I wanted to build.” That site, a tribute to Black musicians in classical and jazz music, turned into much more than a personal project. It opened the door to a whole new career where her creative instincts and curiosity could thrive just as much as they had in music. Now at freeCodeCamp, Jessica maintains and develops the very JavaScript curriculum that has helped her and millions of developers around the world. We spoke with Jessica about her advice for JavaScript learners, why musicians make great developers, and how inclusive communities are helping more women thrive in tech. Jessica’s Top 3 JavaScript Skill Picks for 2025 If you ask Jessica what it takes to succeed as a JavaScript developer in 2025, she won’t point you straight to the newest library or trend. Instead, she lists three skills that sound simple, but take real time to build: > “Learning how to ask questions and research when you get stuck. Learning how to read error messages. And having a strong foundation in the fundamentals” She says those skills don’t come from shortcuts or shiny tools. They come from building. > “Start with small projects and keep building,” she says. “Books like You Don’t Know JS help you understand the theory, but experience comes from writing and shipping code. You learn a lot by doing.” And don’t forget the people around you. > “Meetups and conferences are amazing,” she adds. “You’ll pick up things faster, get feedback, and make friends who are learning alongside you.” Why So Many Musicians End Up in Tech A musical past like Jessica’s isn’t unheard of in the JavaScript industry. In fact, she’s noticed a surprising number of musicians making the leap into software. > “I think it’s because music and code have a lot in common,” she says. “They both require creativity, pattern recognition, problem-solving… and you can really get into flow when you’re deep in either one.” That crossover between artistry and logic feels like home to people who’ve lived in both worlds. What the Tech Community Is Getting Right Jessica has seen both the challenges and the wins when it comes to supporting women in tech. > “There’s still a lot of toxicity in some corners,” she says. “But the communities that are doing it right—like Women Who Code, Women in Tech, and Virtual Coffee—create safe, supportive spaces to grow and share experiences.” She believes those spaces aren’t just helpful, but they’re essential. > “Having a network makes a huge difference, especially early in your career.” What’s Next for Jessica Wilkins? With a catalog of published articles, open-source projects under her belt, and a growing audience of devs following her journey, Jessica is just getting started. She’s still writing. Still mentoring. Still building. And still proving that creativity doesn’t stop at the orchestra pit—it just finds a new stage. Follow Jessica Wilkins on X and Linkedin to keep up with her work in tech, her musical roots, and whatever she’s building next. Sticker illustration by Jacob Ashley....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co