As I ventured to build an authentication workflow in Next.js, I found myself struggling to handle cookies in getServerSideProps.
The Problem
I want to fetch my userds data on the server before rendering the page, meaning Idll first need to validate the access token, which is encrypted in a browser cookie. I want the ability to update, delete, and set multiple new cookies before rendering a page.
Because I need to reuse the functionality for validating the user token, I need the ability to handle cookies in designated functions outside the scope of my getServerSideProps function. For example, making a request to my API from inside getServerSideProps. This is where things get tricky.
In attempting these cookie operations, I quickly realized:
It matters whether you are calling your cookie handling functions from getServerSideProps or normal API endpoints.
I found that when testing my validation endpoints via Postman, the cookies were setting just fine. But when I loaded a page from getServerSideProps, they were not setting in the browser
Why is this the case?
You dondt call getServerSideProps from the browser like you would an API route, therefore you cannot set cookies in the browser using the same methods. We have to work with Next.jsds context object rather than Nodeds request object. Wedll do this with the help of libraries. But the libraries wondt provide us with an integrated approach for passing cookies from functions to getServerSideProps.
Let me explain how I handle cookies in a normal API request before we get into server rendering.
Reading/Writing cookies from the client
I decided to mimic the eases of Expressds cookie handling and add a res.cookie function on my response object. The reason for doing so is that when working in vanilla Node.js, we have to set cookies via:
res.setHeader(“set-cookie”, value)
and as the documentation states:
To send multiple cookies, multiple
Set-Cookie
headers should be sent in the same response.
In other words, you cannot call res.setHeader multiple times and expect the final header to contain all of your cookies. It will only set the header to the last value you passed.
What you have to do instead is pass an array of serialized cookies to the header.
Hereds the blueprint:
I want to have a simple, express-like res.cookie function that adds a cookie to an array, then be able to call a function, res.sendCookies, to actually write the array of cookies to the header.
Using a wrapper
We can add any functions we want to the native req and res objects by exporting our functions inside of a wrapper. This is how wedll use res.cookie.
Hereds what your serverless functions will look like with a wrapper:
import wrapper from 'utils/wrapper';
const handler = async (req, res, next) => {
// code and such
};
export default wrapper(handler);
And this is a peek inside of the wrapper:
import cookie from 'utils/cookiesd;
const wrapper = handler => {
return (req, res, next) => {
res.cookieArray = [];
res.cookie = (name, value, options) => {
cookie(res, name, value, options);
};
res.sendCookies = () => {
res.setHeader('set-cookie', res.cookieArray);
};
return handler(req, res, next);
};
};
res.cookie()
This function sets or deletes a cookie, depending on whether the value argument is empty, by serializing it and pushing it to our res.cookieArray.
import { serialize } from 'cookied';
const cookie = (res, name, value, options) => {
if (!options) options = {};
options.path = '/d;
const stringValue = (typeof value === 'objectd)
? 'j:d + JSON.stringify(value)
: String(value)
if ('maxAged in options) {
options.expires = new Date(Date.now() + options.maxAge);
options.maxAge /= 1000;
};
if (!value) {
options.expires = new Date(Date.now() — 1000);
};
// if we pass a value, it sets the cookie
if (value) {
res.cookieArray.push(serialize(name, String(stringValue), options))
// if we do not pass an empty string as a value, it deletes the cookie
} else {
res.cookieArray.push(serialize(name, 'd, options))
};
}
export default cookie;
With this code, you should be able to handle writing and deleting cookies when you call your API directly from the browser or Postman.
Now, onto handling cookies in getServerSideProps.
Reading cookies from the server
This is fairly simple. It is sufficient to rely on external libraries that parse Next.js’s context object for this job rather than attempt it yourself. I use next-cookies to get cookies on the server:
import cookies from 'next-cookies’;
const c = cookies(context);
const myAuthenticationCookie = c.authenticationToken;
Now for the trickier part:
Writing cookies from the server
We’ve already laid the groundwork in how we handle our cookies in our controller functions and API endpoints by adding a res.cookies method and a res.cookieArray.
Consider my scenario from the beginning. I make a fetch request to an endpoint on my API from getServerSideProps. When it returns a response, I won’t have access to the res object that I’ve been using all of this time to accumulate my cookies. So I simply have to return the cookieArray in my response.
return res.json({
otherData: data,
cookieArray: res.cookieArray
});
Then, in getServerSideProps, I need to parse this cookieArray and write each cookie onto the context object. I created a function that is invoked like this:
parseCookies(res.data.cookieArray, context);
Note that context is Node.js’s native object, originating from here:
export async function getServerSideProps(context) {};
Finally, here is my parseCookies function. To do the heavy lifting of writing to the context object, I am relying on next-cookie (as opposed to next-cookies).
import { useCookie } from 'next-cookie';
function parseCookies(array, context) {
const cookie = useCookie(context);
for (let string of array) {
const arr = string.split(';')
const options = {};
const key = arr[0].split('=')[0];
const value = arr[0].split('=')[1];
arr.shift();
for (let option of arr) {
let key = option.split('=')[0].slice(1);
let value = option.split('=')[1];
if (key === 'Path') key='path'
else if (key === 'Expires') key='expires'
else if (key === 'HttpOnly') key='httpOnly', value=true;
options[key] = value;
};
if (value !== '') cookie.set(key, value, options)
else cookie.remove(key);
};
};
export default parseCookies;
Essentially, this parses the serialized cookie strings that I passed into cookieArray and either sets or deletes each one using the function imported from next-cookie.
Improving your Next.js serverless framework
I am striving to get the most out of Next.jsds API and use an Express-like framework so I can leave my backend logic to controllers rather than stuffing everything in Next.jsds serverless functions. If youdre trying to achieve a similar result, check out my article on creating an Express-like framework in Next.js.