I have encountered an issue regarding fetching data from the getInitialProps function in Next.js

The scenario is this: when a user first visits a page, I make an HTTP request to a distant API which returns me data that I need for the application. I make the request inside the getInitialProps method because I want the content to be fully rendered when I ship the content to the user.

The problem is, when I make this request, the API returns me a session cookie which I need to store inside the browser, not the server that is rendering the content. This cookie will have to be present inside future client-side requests to the API. Otherwise, the API returns me 403.

My question is: If I'm performing this request from the server, and because of that the response also comes back to the server, How can I set the cookie for the browser so that I could make client-side requests to the API?

I tried manipulating the domain option of the cookie but I cannot set another domain. The browser just ignores it.

Here is how my getInitialProps looks like:

static async getInitialProps(appContext) {
        const { Component, ctx, router } = appContext;
        const { store } = ctx;
        let pageProps = {};

        if (Component.getInitialProps) {
            pageProps = await Component.getInitialProps(appContext);
        }

        const { hotelId, reservationId } = router.query;

        if (!hotelId || !reservationId) return { pageProps };

        // Fetching reservation and deal data
        try {
            const { data, errors, session } = await fetchData(hotelId, reservationId);

            if (data) {
                store.dispatch(storeData(data));
            }

        // This works, but the domain will be the frontend server, not the API that I connecting to the fetch the data
        if (session) {
            ctx.res.setHeader('Set-Cookie', session);
        }

        // This doesn't work
        if (session) {
            const manipulatedCookie = session + '; Domain: http://exampe-api.io'
            ctx.res.setHeader('Set-Cookie', manipulatedCookie);
        }

            if (errors && errors.length) {
                store.dispatch(fetchError(errors));
                return { errors };
            } else {
                store.dispatch(clearErrors());
                return {
                    ...pageProps,
                    ...data
                };
            }
        } catch (err) {
            store.dispatch(fetchError(err));

            return { errors: [err] };
        }

        return { pageProps };
    }

The fetchData function is just a function which sends a request to the API. From the response object, I'm extracting the cookie and then assign it to the session variable.


Solution 1:

getInitialProps is executed on the client and server. So when you write your fetching function you have fetch conditionally. Because if you make request on the server-side you have to put absolute url but if you are on the browser you use relative path. another thing that you have to be aware, when you make a request you have to attach the cookie automatically.

in your example you are trying to make the request from _app.js. Next.js uses the App component to initialize the pages. So if you want to show some secret data on the page, do it on that page. _app.js is wrapper for all other components, anything that you return from getInitialProps function of _app.js will be available to all other components in your application. But if you want to display some secret data on a component upon authorization, i think it is better to let that component to fetch the data. Imagine a user logins his account, you have to fetch the data only when user logged in, so other endpoints that does not need authentication will not access to that secret data.

So let's say a user logged in and you want to fetch his secret data. imagine you have page /secret so inside that component I can write like this:

Secret.getInitialProps = async (ctx) => {
  const another = await getSecretData(ctx.req);

  return { superValue: another };
};

getSecretData() is where we should be fetching our secret data. fetching actions are usually stored in /actions/index.js directory. Now we go here and write our fetching function:

 // Since you did not mention which libraries you used, i use `axios` and `js-cookie`. they both are very popular and have easy api.
    import axios from "axios";
    import Cookies from "js-cookie";


    //this function is usually stored in /helpers/utils.js
    // cookies are attached to req.header.cookie
    // you can console.log(req.header.cookie) to see the cookies
    // cookieKey is a  param, we pass jwt when we execute this function
    const getCookieFromReq = (req, cookieKey) => {
      const cookie = req.headers.cookie
        .split(";")
        .find((c) => c.trim().startsWith(`${cookieKey}=`));

      if (!cookie) return undefined;
      return cookie.split("=")[1];
    };

    //anytime we make request we have to attach our jwt 
    //if we are on the server, that means we get a **req** object and we execute above function.
   // if we do not have req, that means we are on browser, and we retrieve the    cookies from browser by the help of our 'js-cookie' library.
    const setAuthHeader = (req) => {
      const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");

      if (token) {
        return {
          headers: { authorization: `Bearer ${token}` },
        };
      }
      return undefined;
    };

    //this is where we fetch our data.
    //if we are on server we use absolute path and if not we use relative
    export const getSecretData = async (req) => {
      const url = req ? "http://localhost:3000/api/v1/secret" : "/api/v1/secret";
      return await axios.get(url, setAuthHeader(req)).then((res) => res.data);
    };

this is how you should implement fetching data in next.js