next.js: Unable to dynamically set response headers in `page.js` components (App router)

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

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 56
  • Comments: 33 (5 by maintainers)

Most upvoted comments

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 🙏

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

buuuuuuuuuuump!!

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.

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

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.

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: {},
  };
};

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

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.

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.

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

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