Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to dynamically set response headers in page.js components (App router) #50914

Closed
1 task done
leonbe02 opened this issue Jun 7, 2023 · 33 comments
Closed
1 task done
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. locked

Comments

@leonbe02
Copy link

leonbe02 commented Jun 7, 2023

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Latest next.js app

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue or a replay of the bug

n/a

To Reproduce

Create an app/page.js file and then cry when you realize there is no way to set a response header based on data you fetched to render the page.

Describe the Bug

When using the Page router, you are able to dynamically set response headers within the getServerSideProps function since it is passed a reference to the response object. When using the App router, there is no method in which you can dynamically set a header on the response based on data fetches that occur when SSR the page. This appears to be a regression or missed feature that wasn't migrated from the Page router.

import getProduct from '@/lib/getProduct.js';

export function getServerSideProps({ res, query }) {
  const product = await getProduct(query.slug);
  if (!product) return { notFound: true };

  let maxAge = 86400;
  if (product.saleEndDate > Date.now()) {
    maxAge = Math.min(
      maxAge,
      Math.floor((product.saleEndDate - Date.now()) / 1000)
    );
  }

  res.setHeader('Surrogate-Key', product.id);
  res.setHeader('Surrogate-Control', `max-age=${maxAge}`);

  return {
    props: { product }
  };
}

Expected Behavior

When using the App router, you can get a read-only copy of the request headers, but I'd expect there to be a method in which I can dynamically set the response header based on data I fetched to render the page server-side.

The main use case for us is to set the Surrogate-Key header which is used by our CDN (Fastly) to programmatically purge the cache via tagging: https://docs.fastly.com/en/guides/working-with-surrogate-keys

The only methods I see for setting response headers currently when using the App router is to apply them in the middleware or in the next.config.js. Both of these options are not aware of the data that was fetched when rendering a page so neither of them are able to set headers using data from the page.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

@leonbe02 leonbe02 added the bug Issue was opened via the bug report template. label Jun 7, 2023
@github-actions github-actions bot added the area: app App directory (appDir: true) label Jun 7, 2023
@mattvb91
Copy link

Have you been able to fix this? This is a massive issue.

@Darshan-Naik
Copy link

Facing same issue, not able to migrate to app routing because of this limitation.

@mattvb91
Copy link

related to #42112 maybe too?

@leonbe02
Copy link
Author

Have you been able to fix this? This is a massive issue.

There aren't any work arounds that I'm aware of, the Next.js folks will need to provide a mechanism for these Server Components to be able to apply response headers during SSR.

@fmnxl
Copy link

fmnxl commented Aug 4, 2023

I've been trying to use this: https://nextjs.org/docs/pages/api-reference/next-config-js/headers.

@mattvb91
Copy link

mattvb91 commented Aug 5, 2023

How would I go about it if my api upstream is responsible for setting the headers? I would retrieve them out of my getInitialProps and set them based on what the API upstream was telling to set it. But I can no longer do that

@Nhollas
Copy link

Nhollas commented Aug 10, 2023

This is massive not having this considering I was doing stuff like this before in pages:

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { req } = context;
  if (req.cookies.QuoteId) {
    return {
      props: {},
    };
  }

  const { userId, getToken } = getAuth(req);

  const response = await externalApiClient.post(
    `/api/gateway/createQuote`,
    null,
    userId
      ? {
          headers: {
            Authorization: `Bearer ${await getToken()}`,
          },
        }
      : {}
  );

  if (response.headers["set-cookie"]) {
    context.res.setHeader("set-cookie", response.headers["set-cookie"]);
  }

  return {
    props: {},
  };
};

@mattvb91
Copy link

@Nhollas pretty much in the same boat as you. And doing it through the middleware isnt really an option either because even if managed to do that it would trigger the external API request twice

@4hmaad
Copy link

4hmaad commented Aug 27, 2023

buuuuuuuuuuump!!

@JacobArrow
Copy link

We are having this exact issue aswell!

@mjyaqubi
Copy link

mjyaqubi commented Sep 2, 2023

Having the same issue while trying to add the last-modified header dynamically!

@nsmith7989
Copy link

Similar issue, looking for workarounds myself.

Anyone know: is it possible to use <meta> tags to represent headers? Is that equivalent to a http header in all cases, or just some like content security policy?

@Daniel3711997
Copy link

any news ?

@hamlim
Copy link
Contributor

hamlim commented Sep 12, 2023

I think the recommended approach is to move that logic up to Next.js Middleware

@leonbe02
Copy link
Author

I think the recommended approach is to move that logic up to Next.js Middleware

If that's recommended, how would I set a response header based on a value that was asynchronously fetched in a page.js component? Do you have an example of how that would work?

@hamlim
Copy link
Contributor

hamlim commented Sep 18, 2023

You can rely on Next.js's caching to de-dupe requests from Middleware and from a page.

So you'd fetch the data you are fetching from your page, within the middleware, use the response to set the cookies. Then the page should see a cache HIT when it performs the same fetch, using cached data to avoid duplicate fetches.

@leonbe02
Copy link
Author

You can rely on Next.js's caching to de-dupe requests from Middleware and from a page.

So you'd fetch the data you are fetching from your page, within the middleware, use the response to set the cookies. Then the page should see a cache HIT when it performs the same fetch, using cached data to avoid duplicate fetches.

So the recommendation is to stuff a copy/pasted duplicate fetch request for every dynamic page within your app into the middleware.js and have a big mapping of url -> fetch ? That seems like a terrible solution. From an ergonomics standpoint, the page.js should be able to define dynamic response headers for that page so that all logic related to the page is co-located.

@ucola
Copy link

ucola commented Sep 19, 2023

We are facing a similar issue. We are using the app router structure and need to set a response header inside the page.tsx file.

Do you have any ideas on how to accomplish this? Or perhaps a method to pass a value to the middleware and set the header there?

@djforth
Copy link

djforth commented Sep 28, 2023

Is there any way to do this without using the middleware?

The issue we are facing is that we need to hit an API to get that status to set the API. But we need to proxy the request, which we were using node-fetch. However, middleware only uses edge so we can't use node-fetch and there seems to be no way to set it as Node.

So we are caught in a catch-22, we can't set the headers in the middle because we don't have the data, and where we can get the data, we set the headers!

If there a solution to this?

@studentIvan
Copy link

studentIvan commented Oct 10, 2023

For people who faced with this problem I can recommend my temporary solution:

  1. setup a custom server, example is here https://github.com/vercel/next.js/tree/canary/examples/custom-server
  2. put your tags into the meta with generateMetadata into other: {} section
  3. disable gzip by setting compress: false in next config
  4. use my gist https://gist.github.com/studentIvan/4a9d765900a63552ec898f826c7116dc

@cvle
Copy link

cvle commented Oct 11, 2023

You can rely on Next.js's caching to de-dupe requests from Middleware and from a page.

Data Cache and deduping is not available on the middleware.

Not being able to set headers in page.ts is the main reason we can't move to App router.

@4hmaad
Copy link

4hmaad commented Oct 15, 2023

@cvle As far as I understand, middleware is for use cases such as authentication etc. Why do you need data cache in a middleware? Even if it did support, using It would make your application very slow because calls to Data Cache still takes 20-100ms. De-duping is not supported because Middleware is a different runtime and it runs separately before the page functions.

Not being able to set headers is definitely frustrating but there are some workarounds as well.

@ucola
Copy link

ucola commented Oct 15, 2023

Not being able to set headers is definitely frustrating but there are some workarounds as well.

We need to set custom headers in our page.ts, can you tell us the workarounds @4hmaad ?

@leonbe02
Copy link
Author

@timneutkens Who do we have to @mention on this issue to get some movement? It would be nice to get an official response from the Next.js folks on possible workarounds and if they are currently working on adding the ability to set dynamic response headers within a Page component using the app router.

@laertesousa
Copy link

bump! Like many other people, this missing feature is the only reason I cannot use the App Router structure.

@julianobrasil
Copy link

julianobrasil commented Oct 27, 2023

This may sound silly, but this isn't the first time (and probably not the last one) I see problems with mutating HTTP request/response headers. On the old router, the setHeader() returns a response object. Have you tried context.res = context.res.setHeader(...)? I don't have a setup to test it here, so forgive me if this is too dumb of a suggestion.

@laertesousa
Copy link

This may sound silly, but this isn't the first time (and probably not the last one) I see problems with mutating HTTP request/response headers. On the old router, the setHeader() returns a response object. Have you tried context.res = context.res.setHeader(...)? I don't have a setup to test it here, so forgive me if this is too dumb of a suggestion.

@julianobrasil As far as I am aware, there is no way to access the response object(o.g context.res) from a page on the new App Router structure. That is the limitation at hand here. We do not have a way to modify the response headers after retrieving context during SSR for a given page.

@ucola
Copy link

ucola commented Nov 1, 2023

Any ideas how to solve this or who we can @mention?

Other idea, does anyone have an idea how to access the contents of a file in the middleware? fs doesn't work in the middleware for me.

@ghankerson
Copy link

I have only found this out a week before we launch a big project. Seriously there is not way to set http headers? I will have to migrate back the the pages router now.

@ghankerson
Copy link

ghankerson commented Nov 3, 2023

This appears to work! Put this in middleware.ts at the root level of the repo.

import { NextResponse } from 'next/server';

export function middleware() {
  const response = NextResponse.next();
  response.headers.set('Cache-Control', 'public, max-age: 60');
  return response;
}

Hmm wipes out the etag too then but maybe I can figure that out by taking a hash of the html response or something along those lines.

Hmm the middleware runs before page is rendered so that makes it tricky.

@leerob
Copy link
Member

leerob commented Nov 4, 2023

We're going to post a discussion next week with more details about setting headers outside Middleware and invite feedback on the use cases / product requirements you're looking for 🙏

@gnoff
Copy link
Contributor

gnoff commented Nov 6, 2023

Hey folks,

I've written up a bit about our reasons for not currently having a way to set arbitrary headers during a render with App Router here: #58110

The short version is, We stream the responses while we render in App Router and headers generally have to go before the body. I'd encourage everyone here who feels strongly that there should be an API like the one proposed here to contribute to the discussion by providing more details about your use case.

I'm going to close this issue for now because we are not going to add the API as proposed here

@gnoff gnoff closed this as completed Nov 6, 2023
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 21, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. locked
Projects
None yet
Development

No branches or pull requests