Next.JS 13.4 Cookies() Function Works In Route Handler In Development But Not Production

777 views Asked by At

This seems to be touched on in this question Next.js 13 Cookie value not retrieved from Route Handler, but the answer there isn't working for me.

I have a Nextjs 13.4 frontend and a Rails 7 API-only backend with Doorkeeper configured to authenticate with an HTTP-Only cookie.

I've implemented Next.js Route Handlers to make API calls to the backend that need to include the cookie for create/update/delete routes.

In development, working with localhost:3000 on the front and localhost:3001 on the back, I can retrieve the access_token cookie from the browser using either the Next.js cookies() function or with the Request object via req.cookies() function and pass it along to the Rails API request.

However, when I'm in production, where I have the Next.js app hosted on Netlify and the backend hosted on Heroku (cross-origin), both cookies functions return an empty object, even though I can see the access_token stored correctly in the browser using devTools.

In production the cookie is set as Secure, sameSite=None and HttpOnly, which should allow for cross-origin sharing. Both apps communicate via https. I've spent days now trying to get this to work and I'm starting to think it's a bug in Next.js, but maybe someone with more knowledge on this subject than me might see an issue with my code below.

Following the path of an update form submission, I first make a fetch call from the update form, a client component, to the PUT route handler. I'm including credentials in the fetch request, which by my understanding should send the cookies along in the request object:

form.component.tsx

// handlers
  const submitHandler: SubmitHandler<ProductFormData> = async (
    formData: ProductFormData
  ) => {
    setLoading(true);

    // format form data for fetch request
    const encodedData = encodeProductFormData(formData, defaultImage, images);

    try {
      // update
      if (product) { 
        const response = await fetch(`/api/products/${ product.slug }`, {
          credentials: 'include',
          method: 'PUT',
          body: encodedData
        });
      } else { 
        // create
        const response = await fetch('/api/products', {
          credentials: 'include',
          method: 'POST',
          body: encodedData
        });
      }

      router.push('/');
    } catch (error) {
      setLoading(false);
      console.error('Something went wrong:', error);
    }
  };

That request comes through to the PUT route handler, where I need to extract the access_token cookie from either the request object or the cookies() function. As you can see below, I am using request.cookies.get('access_token'). In development, this works perfectly. The cookie is retrieved and sent correctly to the updateProduct() API call to the external Rails server. In production, if I console log the request object, I can see that the cookies are empty, even though the access_token is present and correctly set in the browser.

/api/products/[slug]/route.ts

export const PUT = async (
  request: NextRequest,
  { params }: { params: { slug: string } } 
) => {
  // extract doorkeeper auth token from cookies
  const token = request.cookies.get('access_token');

  // get slug from params
  const slug = params.slug;

  // get form data from request
  const formData = await request.formData();

  // send to /v1/products#update endpoint
  const product = await updateProduct(slug, formData, token);

  // configure response
  const response = NextResponse.json(
    { product: product },
    { status: 200 }
  );

  // refresh data & router cache
  revalidatePath('/');

  return response;
};

I'm trying to figure out why this isn't working in production. What might I be missing here that could be preventing cookies from getting passed to the route handler?

The cookies DO get sent if I make a direct call from the form to the Rails backend, but then I can't call revalidatePath and refresh the cache, so the changes don't get updated on the frontend because of Next.js' frustrating caching system.

0

There are 0 answers