1

I have a Nextjs 13 frontend (app router) that communicates with a Laravel-powered backend via api. For authentication in the api, I'm using Laravel Sanctum (as recommended by Laravel for SPAs): basically, authentication works by setting two cookies (a session and a CSRF token) after login, which should then be attached to all future requests, along with a X-Xsrf-Token header that passes the CSRF token.

To send a request from a client component, I can do:

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" ),
      // Since we're on the browser, cookies are automatically attached via the 'Cookie' header
   },
   credentials: 'include'
})

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

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

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

Both fetchers work. However, sometimes I need to make the same request both from a server component and from a client component, so I would like to use a single fetcher like this (this is a quick and dirty example just to get my point across):

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

fetch( [url], 
   isServer 
   ?
   {
      headers: {
         "Cookie": cookies().toString(), // Must attach cookies manually
         "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" ),
         // Since we're on the browser, cookies are automatically attached via the 'Cookie' header
      },
      credentials: 'include'
   } 
)

But this throws an error in client components because the cookies() and headers() functions can only be used in server components.

So, in short, how can I write a single fetcher that works both on server and client components?

grazdev
  • 1,082
  • 2
  • 15
  • 37

1 Answers1

-1

True, it's not possible to use the cookies() and headers() functions in client components as they are server functions. These functions are designed to be used in server components or server actions.

However, you can create a utility function that checks if the code is running on the server or the client and then performs the fetch accordingly. Here's a basic 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);
}

This universalFetch function first checks if it's running on the server or the client using the isServer function. If it's on the server, it uses the serverCookies and headers functions from next/headers to set the headers. If it's on the client, it uses the cookies function from js-cookie to get the XSRF-TOKEN and sets the credentials option to 'include'.

CyberJ
  • 1,018
  • 1
  • 11
  • 24
  • Welcome back to Stack Overflow, CyberJ. It looks like it's been a while since you've posted and may not be aware of the current policies since at least *eight* of of your answers yesterday appear likely to have been entirely or partially written by AI (e.g., ChatGPT). Please be aware that [posting AI-generated content is not allowed here](//meta.stackoverflow.com/q/421831). If you used an AI tool to assist with any answer, I would encourage you to delete it. We do hope you'll stick around and continue to be a valuable part of our community by posting *your own* quality content. Thanks! – NotTheDr01ds Jul 23 '23 at 16:15
  • **Readers should review this answer carefully and critically, as AI-generated information often contains fundamental errors and misinformation.** If you observe quality issues and/or have reason to believe that this answer was generated by AI, please leave feedback accordingly. – NotTheDr01ds Jul 23 '23 at 16:15