App Router Custom Header Use Cases #58110
Replies: 20 comments 39 replies
-
Thanks for the discussion, I'll try to explain our case What capability are you aiming to achieve? (For example, "I want my web pages to be cached by the browser.") Each piece of content in Drupal has different cache tags. We transfer these cache tags to Next.js and use them there for cache invalidation, which is currently working well. However, we also need to pass these cache tags to Varnish (via headers) so that Varnish can utilize them as well. Which header or headers are necessary to coordinate this behavior? Why are middleware and static headers insufficient? |
Beta Was this translation helpful? Give feedback.
-
What capability are you aiming to achieve? (For example, "I want my web pages to be cached by the browser.") We use a headless API as the backend that follows a page model approach (send in pathname as parameter). We use a catchall approach with dynamic routing and have no static routes. And we have two usecases
CSP is just an example of an header which you might want to set dynamically. I can imagine other headers, for example response headers from the API that you want to forward to the frontend for traceability (correlationid, idempotentcy, open trace id, x-sentry, x-service, x-app, x-team custom headers) Which header or headers are necessary to coordinate this behavior? Why are middleware and static headers insufficient? |
Beta Was this translation helpful? Give feedback.
-
Your reasoning on streaming content makes sense, but when you run a public website where content is usually the same but occasionally updated and you want to use a CDN - the app router almost leaves you without options. 1. What capability do you want to achieve? (i.e. I'd like my pages to be cached by the browser) Here is what I am looking for
So in this scenario I want the browser to send an if none match header to the CDN with etag in this case If it's been over 60 seconds the CDN then contacts the site sending the same if none match header and then the site sends a response if the etag is still the same from the webserver (
As far as I can tell there is no way to do this in the app router. Middleware let's you set Cache-control but if you set Cache-Control in middle it wipes out the etag. Since Middleware runs before the response there isn't a good way for me to set an etag based on the content of the page being served. I understand app router is trying to stream content as it becomes available, but that is at odds with what you need to serve content via a CDN in my view. As far as I can tell on app router I can either get Cache-Control: private; max-age: 0; must revalidate and no etag OR I can get an et tag with cache-control: public; s-max-age: 31,600,000. (one year!) OR override cache-control in middleware but then get no etag. If you think I'm doing it wrong - let me know as it is - I might go back to pages router. It seems app router is great for an app that doesn't use a CDN and servers customized content to a logged in user but when you want to serve the same content to everyone and use a CDN you are out of luck. Can't emphasize enough how much setting cache-control in middleware nuking the etag is a disappointment and a problem. If setting cache-control in middleware didn't clobber the etag then there would be a solution. |
Beta Was this translation helpful? Give feedback.
-
Just in terms of "What makes Middleware and or static headers insufficient":
|
Beta Was this translation helpful? Give feedback.
-
We have two use cases:
Both can be solved using |
Beta Was this translation helpful? Give feedback.
-
What capability do you want to achieve? (i.e. I'd like my pages to be cached by the browser) At Pantheon we're looking to preserve the CMS publishing flows (through WordPress and Drupal) that our customers expect while pairing those CMSes with Next.js (and other front-end frameworks). So at the highest level, our use case is that we would like to be able to invalidate dynamic Next.js routes from the CDN based on cache tag values included in a custom When using either Drupal or WordPress as a backend, our customers can enable the Pantheon Advanced Page Cache module/plugin to include this cache tag data on responses, including the data APIs provided by either CMS. When content is updated in the CMS, the Advanced Page Cache plugin will issue a purge request to our CDN which will revalidate any route dependent on the provided cache keys - both for the CMS providing that data, and the Next.js route consuming it. The following documentation provides some more detail on how we’ve implemented this in our Drupal and WordPress Next.js starter kits using the pages router: While we’ve focused on WordPress and Drupal due to our existing user base, we see this approach as something that should be able to apply to any content source. This doesn’t prevent us from also supporting framework specific solutions to this problem like On-Demand Revalidation, but generally we prefer to put more reliance on the CDN than inside the application. Being able to offer a consistent cache-tag based invalidation strategy across the entire platform also opens up some powerful possibilities for our users. We’d like them to have this option with Next.js. This is possible today with the pages router, but not yet practical from our perspective today using the app router. What header or headers are necessary to coordinate this behavior?
What makes middleware and static headers insufficient Our prototypical customers are
Frequently publishing means that it is impractical to set cache tags statically, or in a way that requires a front-end code deployment. The cache tags on the homepage in any given minute might change in the next minute when another piece of content is published. Soon after the app router became stable we looked at implementing this in our Next.js starter kits for WordPress and Drupal. We encountered similar problems to others in this thread. Middleware runs too early for many of our use cases. Certain data fetching (for instance, does a given page need to fetch the data for a sidebar of recently published articles in a taxonomy term?) will only be knowable after rendering starts. If we were to use middleware, it would be necessary to move all data fetching out of components and into the middleware file. There can also only be a single top level middleware file, which means that all data fetching would have to happen in a single middleware file, or we’d have to invent some convention to be able to import modules from elsewhere in the application into the middleware. Either way, we don’t see any of these approaches as something we can broadly recommend to customers and expect them to reliably implement.
We encountered this as well. At the time we decided to set this research aside we still needed to make a second redundant request outside of the middleware. It might be possible to solve this via caching or something similar, but we didn’t get that far. Additional comments We’re seeing more users using the app router, likely as a side effect of it being presented as the default in the Next.js cli and in documentation. While some of these cases can be solved by the pages router, redirecting them that way to solve these problems seems impractical if app router usage continues to grow. Especially with likely high-visibility features like partial prerendering on the horizon. Appreciate all the discussion here — happy to provide any additional information that could help. |
Beta Was this translation helpful? Give feedback.
-
I'm giving up on app router for now. I've tried to make it work but I can't figure it out. I thought incremental static regeneration would get me there but I am finding I get different etags per Kubernetes pod so this makes using a CDN very difficult. This is disappointing because lighthouse scores using Next 13 are much improved. At the end of the day though I am seeing too much server load largely due to the inability to use a CDN effectively. Maybe this all gets ironed out soon and I can return to the app router. |
Beta Was this translation helpful? Give feedback.
-
I have a related use case that I've opened a separate discussion for: #58303. I'll rephrase it to match the format of this discussion. What capability do you want to achieve? I'd like to use an appropriate Another use case is caching of pages with search params (e.g. Something like an opt-in What header or headers are necessary to coordinate this behavior? The What makes Middleware and or static headers insufficient
|
Beta Was this translation helpful? Give feedback.
-
Im usgin the middleware to add the |
Beta Was this translation helpful? Give feedback.
-
In my project using Next.js and a Headless CMS, combined with Cloudflare, the primary goal is to optimize content updates through an efficient purging strategy. To achieve this, I'm planning to use cache tag headers, such as 'global-${block.id}' for global blocks and 'page-{$page.id}' for pages, based on the CMS's response. These tags are crucial not just for caching but more importantly for purging. They enable selective clearing of the cache, allowing me to specifically target and purge only the content that has been updated. This approach is vital for maintaining content up-to-date with related pages or entities within the CMS, ensuring that any updates are reflected promptly without needing to purge the entire cache. This approach, if I could set headers directly through Next.js App Router, would provide the simplest solution while also allowing me to leverage tiered caching in Cloudflare. |
Beta Was this translation helpful? Give feedback.
-
What capability do you want to achieve? (i.e. I'd like my pages to be cached by the browser)I would like to precisely control the caching behavior of Vercel network and/or the browser by telling it when a page is finalized and becomes immutable. In my case, I have a "task" object fetched from the database (not via the What header or headers are necessary to coordinate this behavior?
What makes Middleware and or static headers insufficientFetching requested "task" objects inside the middleware to determine if it is finalized is impossible and does not sound like a good engineering practice. 1) The database SDK I am using does not run on Edge Runtime. 2) Even if it is possible, it would introduce unnecessary latency due to fetching the same "task" objects twice. |
Beta Was this translation helpful? Give feedback.
-
Just wanted to post a note here that you can get http headers on Next's app router set like this
To do this you just have to add this to your page.tsx/page.jsx file:
The only issue now is I get different etags sometimes per AWS EC2 instance. The problem for me was the nomenclature in the Next documentation. What I think of is http caching or maybe page level caching is not called this in the documentation. The documentation talks about 4 kinds of caching but never http caching https://nextjs.org/docs/app/building-your-application/caching if I am not mistaken. |
Beta Was this translation helpful? Give feedback.
-
What capability do you want to achieve?I'd like to use a CDN. For example I have a product page, and I want it to be served by the CDN (using a stale while revalidate approach). The page is forced to be dynamic because it access the What header or headers are necessary to coordinate this behaviour?
What makes Middleware and or static headers insufficientThe fact that Next.js overrides |
Beta Was this translation helpful? Give feedback.
-
We also run into the need to set the |
Beta Was this translation helpful? Give feedback.
-
What capability do you want to achieve?I'd like to perform server-side authentication or identification using a mechanism that may set cookies, e.g. because it has refreshed an access token and wants to pass the new version down. @auth0/nextjs-auth0's
What header or headers are necessary to coordinate this behavior?
What makes Middleware and or static headers insufficientMiddleware is a poor fit for two reasons:
Static headers are insufficient because this header must be unique per user and changes over the life of their session. |
Beta Was this translation helpful? Give feedback.
-
What capability do you want to achieve?I'd like to render flash messages that have been stored in a cookie (in another This is all to support non-JS clients. What header or headers are necessary to coordinate this behavior?
What makes Middleware and or static headers insufficientMiddleware is a poor fit because I only want to delete the cookie after having rendered the layout. It's also possible that other things in my system render HTML -- for example, a Static headers may be a good fit for this if they have |
Beta Was this translation helpful? Give feedback.
-
What capability do you want to achieve?I'd like to secure embeddable content for a multi-tenant application. What header or headers are necessary to coordinate this behavior?
What makes Middleware and or static headers insufficientStatic headers are insufficient as our goal is to provide this functionality for each potential tenant on our SaaS. |
Beta Was this translation helpful? Give feedback.
-
What capability do you want to achieve? We have a route After we did authenticate the users ip we want to store the authToken in a cookie. So we don't have to authenticate again. What header or headers are necessary to coordinate this behavior?
What makes Middleware and or static headers insufficient We would need to fetch the resource information in the middleware for all resources under this path. |
Beta Was this translation helpful? Give feedback.
-
I'm aiming to use the Server-Timing header-based API, which is the recommended to debug high TTFB applications (https://web.dev/articles/optimize-ttfb#understanding_high_ttfb_with_server-timing). This requires me to be able to measure the time taken to perform certain DB calls during the RSC render, and then supply I understand this use case is likely impossible given the RSC streaming, so I suppose I could also log timings to a central logging application and then set up a pipeline to extract timings on those logs, but that feels a bit overkill. |
Beta Was this translation helpful? Give feedback.
-
Our use-case is to set an
This is pretty unmanageable at the middleware level |
Beta Was this translation helpful? Give feedback.
-
We’ve noticed questions and an interest in being able to set headers on the Response during an App Router render. We wanted to clarify why we don’t provide a generic solution for this at this time and ask for a certain kind of feedback that may influence our path forward here in the future.
Background
The Pages Router, unlike the App Router, does not stream responses. Because of this, the Pages Router can set any headers discovered during rendering before we send the Response body.
The App Router uses React streaming for Server Side Rendering (SSR) and React Server Components (RSC). With streaming, we may have already starting sending a portion of the body before a component could attempt to set headers. This makes traditional APIs where you set a header while rendering impractical for App Router.
Our current solution
We recommend setting headers in Middleware today when using the App Router. While this works, we believe there’s still some opportunities for improvement here:
Why not a generic solution yet?
We could offer an API like
generateHeaders() {...}
. This API could block the stream until the function resolved. Further, it would share aRequest
cache with the render process and run concurrently with it.While this would solve some of the tradeoffs of the current Middleware solution, we are concerned about the unintentional performance impacts and footguns.
Properly setting headers can have a massive performance impact. Further, generic APIs to set headers would not be integrated with other purpose designed APIs for caching and revalidating, like Incremental Static Regeneration (ISR).
For example, if you used a generic headers API and also ISR on the same page, you would have unexpected behavior. Combining these together may cause browsers to avoid refetching even after an ISR revalidation is triggered.
This is why ISR does set
Cache-Control
headers, but it derives through values from higher level abstractions that can be discovered (and possible errored on) during build time.Purpose of this discussion
We’d like to use this discussion to understand the underlying use cases that lead folks to look for a generic headers API beyond Middleware. In some cases we may find that a purpose-fit API is necessary to provide the optimal experience and avoid footguns leading to poor performance and frustration. We may also discover there are cases where a generic and simple headers API is really the right fit.
Before posting, please consider whether the use case actually requires concurrent-with-render-time derivation. Middleware is a great place for headers that don't have an IO component. Additionally if your use-case is that you need to use Node.js APIs please still be specific about what you are trying to accomplish, not just that you are using a tool which is incompatible with Middleware.
Additionally, many hosts (including Vercel) allow you to statically assign headers by route. If your header use case can be satisfied by one of these techniques, then it doesn't need to be documented in this discussion.
A use case here should convey the following information:
We are aware there is a need for many potential users that isn't yet being met as well as we'd like.
I do not want to have this discussion turn into one about what the right API is. Please keep the discussion focussed on what use cases need to be served which are currently impossible or impractical today.
Beta Was this translation helpful? Give feedback.
All reactions