Creating a fetcher that seamlessly functions on both the server and client within Nextjs 13 - the ultimate guide!

My Nextjs 13 frontend (app router) interacts with a Laravel-powered backend through an api. To handle authentication in the api, I am utilizing Laravel Sanctum as suggested by Laravel for SPAs. This involves setting two cookies (a session and a CSRF token) after login, which must be included in all future requests along with a X-Xsrf-Token header that carries the CSRF token.

To make a request from a client component, I can use the following code:

import cookies from 'js-cookie'

fetch( [url], {
   headers: {
      "Accept": "application/json",
      "Content-Type": "application/json",
      "Origin": [origin],
      "X-Requested-With": "XMLHttpRequest",  
      "X-Xsrf-Token": cookies.get( "XSRF-TOKEN" ),
      // Cookies are automatically attached via the 'Cookie' header on browser
   },
   credentials: 'include'
})

If I need to send a request from a server component, I can do:

import { cookies, headers } from "next/headers"

fetch( [url], {
   headers: {
      "Cookie": cookies().toString(), 
      "Referer": headers().get( "referer" ) ?? "",
      "X-Xsrf-Token": cookies().get( "XSRF-TOKEN" ).value,
   },
})

While both methods work, sometimes I need to send the same request from both server and client components. In such cases, I want to create a single fetcher like this:

import cookies from 'js-cookie'
import { cookies, headers } from "next/headers"

fetch( [url], 
   isServer 
   ?
   {
      headers: {
         "Cookie": cookies().toString(),
         "Referer": headers().get( "referer" ) ?? "",
         "X-Xsrf-Token": cookies().get( "XSRF-TOKEN" ).value,
      }   
   }
   :
   {
      headers: {
         "Accept": "application/json",
         "Content-Type": "application/json",
         "Origin": process.env.NEXT_PUBLIC_APP_URL,
         "X-Requested-With": "XMLHttpRequest",  
         "X-Xsrf-Token": cookies.get( "XSRF-TOKEN" ),
      },
      credentials: 'include'
   } 
)

However, this setup causes errors in client components due to restrictions on using cookies() and headers() functions outside of server components. So, my question is how can I create a universal fetcher that functions seamlessly on both server and client components?

Answer №1

Yes, the cookies() and headers() functions are meant for server-side use only and cannot be used in client components. These functions are specifically designed to be utilized in server components or server actions.

However, it is possible to create a utility function that can determine if the code is executing on the server or the client, and then execute the fetch accordingly. Below is a simple example:

import cookies from 'js-cookie'
import { cookies as serverCookies, headers } from "next/headers"

function isServer() {
  return typeof window === 'undefined';
}

async function universalFetch(url, options = {}) {
  if (isServer()) {
    options.headers = {
      ...options.headers,
      "Cookie": serverCookies().toString(),
      "Referer": headers().get("referer") ?? "",
      "X-Xsrf-Token": serverCookies().get("XSRF-TOKEN").value,
    };
  } else {
    options.headers = {
      ...options.headers,
      "Accept": "application/json",
      "Content-Type": "application/json",
      "Origin": process.env.NEXT_PUBLIC_APP_URL,
      "X-Requested-With": "XMLHttpRequest",  
      "X-Xsrf-Token": cookies.get("XSRF-TOKEN"),
    };
    options.credentials = 'include';
  }

  return fetch(url, options);
}

In this universalFetch function, it first determines whether it is being executed on the server or the client using the isServer function. If running on the server, it utilizes the serverCookies and headers functions from next/headers to set the necessary headers. On the other hand, when running on the client side, it uses the cookies function from js-cookie to retrieve the XSRF-TOKEN and includes the credentials option set to 'include'.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

React app experiencing inconsistent loading of Google Translate script

In my React application, I have integrated the Google Translate script to enable translation functionality. However, I am facing an issue where the translator dropdown does not consistently appear on every page visit. While it sometimes loads properly, oth ...

issue with replicated field

My code is working perfectly fine, as you can see here. Now, I am attempting to replace <input id="input1" /> with <div id="input1"> </div>. You can view my progress here. The issue lies in the IDs - they are meant to change from input1 ...

Enhancing nested mongoose arrays by a particular value for updates

In my mongoose schema, I have an array that contains items and another array. var mongoose = require('mongoose'); var Schema = mongoose.Schema; var CompanySchema = new Schema({ dateCreated: { type: Date, default: Date.now }, ownerId: { typ ...

"Refine Your Grid with a Pinterest-Inspired

I've set up a grid of images in columns similar to Pinterest that I would like to filter. The images vary in height but all have the same width. The issue arises when a taller image is followed by a shorter one, causing the short image to float right ...

Error occurs while attempting to parallel/intercept routes within a route group in Next.js version 13.4

My current folder structure is as follows: https://i.stack.imgur.com/9nB2P.png Recently, I was working on implementing modals in my Next.js application following the documentation provided by Next.js. I am trying to replicate a modal similar to their nex ...

chart.js version 3 does not display the correct date data on the time axis

Struggling to make chart.js work with a time axis is proving to be quite challenging for me. The code I have is as follows: <html> <head> <script src="https://cdn.jsdelivr.net/npm/moment"></script> <script src="https://cdnjs.clo ...

JavaScript/DOM - What sets apart a "CSS Selector" from an attribute?

When it comes to excluding declarative event handlers: <a href='#' onclick=<handler> ... /> Is there a significant difference between an Attribute and a CSS Selector? For example, if I define my own attribute: <a href='#&a ...

Child component in Vue 2 not receiving defined props

After numerous attempts, I am still struggling to successfully pass a prop down to a child component. <Project :grossMarginPerResource="grossMarginPerResource" :threshold="threshold" :orderBy="orderBy" @refetch="refetc ...

Activate an event on a separate webpage using jQuery or JavaScript

Currently, I am in the process of working on a project with 2 distinct pages: Index Settings On the Settings page, I have implemented a button to close an element and hide it. However, I am encountering an issue where I want the elements on the Index pa ...

Angular 6 - Consistently returning a value of -1

I'm facing an issue with updating a record in my service where the changes are not being reflected in the component's data. listData contains all the necessary information. All variables have relevant data stored in them. For example: 1, 1, my ...

Enhancing performance by dynamically updating DOM elements when they come into view during scrolling

Currently, I am in search of an efficient algorithm to dynamically load background-images for a group of <li>'s but I am encountering some efficiency issues. The code I am using at the moment is as follows: function elementInView($elem, vps, vp ...

How to make a HTML div background fill the entire page

Is there a way to extend the background in a div so that it adjusts to fit the screen resolution? I also want to include a navigation bar at the top right corner of this div for the intro page. There will be additional content in other divs positioned be ...

The error message "Attempting to send a message using an undefined 'send' property in the welcomeChannel" is displayed

bot.on('guildMemberAdd', (member) => { console.log(member) const welcomeChannel = member.guild.channels.cache.find(channel => channel.name === 'welcome'); //const channelId = '789052445485563935' // welcome c ...

Tips on transmitting and receiving substantial JSON information

As a newbie in the full-stack development world, I am currently on a quest to find an efficient method for transmitting and retrieving large data between my React front-end and Express back-end while keeping memory usage to a minimum. My project involves b ...

Determine if the input number exceeds a specified threshold by implementing JavaScript

My attempt at performing calculations on a page is not yielding the desired results. I have tinkered with the code below, but it doesn't seem to be working as expected. Can anyone provide guidance on where I might be going wrong? Specifically, I want ...

Encountering no automatic refresh feature in Next.js

Encountering an issue with Next.js where the local host index page doesn't automatically refresh whenever a change is made. To provide some context, I initiated a Next.js application using npx create-next-app --use-npm. After starting the local serve ...

Transmit a JSON array from a controller to a JavaScript variable

I have retrieved a JSON array from a database and it is set up in a controller like this: public ActionResult highlight() { var statesHighlight = db.Jobs .Select(r => r.State); return Json(statesHighlight , JsonRequestBehavi ...

Is it considered poor practice to employ setTimeout to ensure that a function is executed after the useEffect hook has run?

Let's imagine I have the following code snippet: const [test, setTest] = useState(); const [test2, setTest2] = useState(); useEffect(() => { setTest2(undefined); }, [test]); const calledFunction => () { setTest(whatever); setTest2(this ...

"Implementing Scrollify.js to dynamically add a class to the current section

I'm attempting to dynamically add an 'active' class to the current section on a page using scrollify.js. While I can retrieve the index value, I am struggling to get the section id or class. How can I obtain the id or class of the current s ...

Tips for incorporating a transition effect on group hover using Tailwind CSS

I've been working on creating a cool animated transition effect for my button where a chevron icon appears when the user hovers over it. I'm currently developing the website using NextJS and TailwindCSS, and I've implemented a group hover fe ...