Netlify Function with Cache-Control


I’m trying to make a really simple Netlify Function that accepts a query param, does some magic based on that and generates some output. The output is supposed to be the same across all the calls of the Function as long as the given param is the same.

To achieve this I added ‘Cache-Control’: ‘max-age=365000000,immutable’ header to my function result.
Now the problem is Netlify caches the Function response based only on the path to the Function, not taking into account the param. (Although, this cache seems to get invalidated in not so long time.)

See on an example. Here is a function that says Hello ${name} when given name query param, or Hello World otherwise.
If you curl first and then it will keep calling you Yaroslav for some time no matter what name you actually provide.

This is what it looks like inside:

exports.handler = function (event, context, callback) {
  const { name } = event.queryStringParameters;

  callback(null, {
    statusCode: 200,
    headers: {
      'Cache-Control': 'max-age=365000000,immutable',
    body: 'Hello, ' + (name || 'World') + '. ' + new Date().toISOString(),

Is this expected? If so, how can I implement such an eager caching in Netlify Functions otherwise?

Hi, I tested your function in and while I do see what you described, as soon as I used an incognito browser window, the function returned a ‘hello world’ instead. I think what you are seeing is how caching behaves in the browser. Not sure what you can do to avoid that except to not send that cache-control header. Or change your request to a post and send your arguments in the request body.

That’s definitely not a stale browser cache :slight_smile:

~ $ curl -v ''
*   Trying
* Connected to ( port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: OU=Domain Control Validated; CN=*
*  start date: Mar  4 11:17:06 2020 GMT
*  expire date: Mar  5 11:17:06 2021 GMT
*  subjectAltName: host "" matched cert's "*"
*  issuer: C=BE; O=GlobalSign nv-sa; CN=AlphaSSL CA - SHA256 - G2
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fcbd900d600)
> GET /.netlify/functions/hello HTTP/2
> Host:
> User-Agent: curl/7.64.1
> Accept: */*
* Connection state changed (MAX_CONCURRENT_STREAMS == 150)!
< HTTP/2 200 
< cache-control: max-age=365000000,immutable
< date: Sat, 23 May 2020 22:18:45 GMT
< content-length: 41
< content-type: text/plain; charset=utf-8
< age: 155995
< server: Netlify
< x-nf-request-id: d5f08243-c2eb-47b5-ada0-825106912929-3070234
* Connection #0 to host left intact
Hello, Yaroslav. 2020-05-23T22:18:45.039Z* Closing connection 0

This is what I see right now, after 2 days (note the Date: header).

I think when you tested the function in Incognito you might have hit another CDN load balancer which didn’t have the response in the cache yet. Have you tried to hard-refreshing the page several times? Otherwise try using cURL on that endpoint and calling it several times.

I’m not sure CDN is supposed to ignore query parameters when caching responses as per HTTP spec. Do you think so?

Hi! Ah, I see! You are correct. We should probably be caching requests with different query string params separately but it looks like it is not. I’ve dug around and do see an open issue around this issue and have added this thread to the issue so if there are any changes, we’ll update here.

1 Like