Repository: anuraghazra/github-readme-stats
Pull request #2124 (2022-10-04 22:58:05)

feat: improve card loading speed
This commit ensures that both the private browser and shared CND caches are used. By doing this, the load on both the Vercel instance, the GraphQL and WakaAPI is reduced. In addition, the `stale-while-revalidate` option was added to speed up the card loading. I now use the vercel recommendations while also keeping a browser cache to lower the demand on the vercel instance (see 
https://vercel.com/docs/concepts/functions/serverless-functions/edge-caching). I am however still researching whether there is a difference between `public, maxage` and `s-maxage` and how `stale-while-revalidate` behaves when no value is supplied. 

vercel[bot]: **The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated |
| :--- | :----- | :------ | :------ |
| **github-readme-stats** | 🔄 Building ([Inspect](https://vercel.com/github-readme-stats-team/github-readme-stats/FeHtco9fCKaRceeifn8Mt2EyG6Mp)) |  | Oct 4, 2022 at 10:58PM (UTC) |


rickstaa: @anuraghazra I did some tests and determined that the `stale-while-revalidate` flag requires a value for it to work (compare https://s-w-r-demo.glitch.me/ with https://s-w-r-demo-default.glitch.me/). Further, I found [this issue](https://github.com/symfony/symfony-docs/issues/12934), which explains why it is better to use `public, max-age over `s-maxage`. I, therefore, suggest we only add the `stale-while-revalidate=<24 hours>`. This will not improve our coaching behaviour but increase card loading speeds if multiple requests are made within 24 hours. With this behaviour, people will see the stale server response, but this response is updated in the background for the next visitor.


codecov[bot]: # [Codecov](https://codecov.io/gh/anuraghazra/github-readme-stats/pull/2124?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) Report
Base: **96.65**% // Head: **96.65**% // Increases project coverage by **`+0.00%`** :tada:
> Coverage data is based on head [(`fbc3b76`)](https://codecov.io/gh/anuraghazra/github-readme-stats/pull/2124?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) compared to base [(`bd9865d`)](https://codecov.io/gh/anuraghazra/github-readme-stats/commit/bd9865d5a2c9effda41bd5a66b7e01c02af7e2be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra).
> Patch coverage: 100.00% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #2124   +/-   ##
=======================================
  Coverage   96.65%   96.65%           
=======================================
  Files          22       22           
  Lines        3792     3801    +9     
  Branches      323      323           
=======================================
+ Hits         3665     3674    +9     
  Misses        125      125           
  Partials        2        2           
```


| [Impacted Files](https://codecov.io/gh/anuraghazra/github-readme-stats/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) | Coverage Δ | |
|---|---|---|
| [api/index.js](https://codecov.io/gh/anuraghazra/github-readme-stats/pull/2124/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra#diff-YXBpL2luZGV4Lmpz) | `95.87% <100.00%> (+0.13%)` | :arrow_up: |
| [api/pin.js](https://codecov.io/gh/anuraghazra/github-readme-stats/pull/2124/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra#diff-YXBpL3Bpbi5qcw==) | `92.77% <100.00%> (+0.27%)` | :arrow_up: |
| [api/top-langs.js](https://codecov.io/gh/anuraghazra/github-readme-stats/pull/2124/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra#diff-YXBpL3RvcC1sYW5ncy5qcw==) | `95.23% <100.00%> (+0.17%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/anuraghazra/github-readme-stats/pull/2124?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra).

rickstaa: @anuraghazra I think the current header makes sense:

```
"Cache-Control: max-age=${cacheSeconds / 2}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}"
```

Using this header, since `s-maxage` is used, the cache is stored both by the browser and the Vercel CDN with different time refresh rates. If the cache is no longer fresh on the browser, it will be requested from the CDN. If the cache is no longer fresh on the CDN, it will return a stale response and update on the server. Using this header, we get the best latency and reduce the load on the GraphQL API. See https://stackoverflow.com/a/74098171/8135687 for more information.

I now have set the private age to be half the CDN time, meaning that the browser is updated two times as frequent as the CDN. Doing this will give users an update faster if they request the cards near the end of the CDN staleness period. However, having the `maxage` and `s-maxage` equal is also an option.

Patch:
diff --git a/api/index.js b/api/index.js
index 8fbe2bc..b449d43 100644
--- a/api/index.js
+++ b/api/index.js
@@ -61,7 +61,12 @@ export default async (req, res) => {
       CONSTANTS.ONE_DAY,
     );
 
-    res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
+    res.setHeader(
+      "Cache-Control",
+      `max-age=${
+        cacheSeconds / 2
+      }, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
+    );
 
     return res.send(
       renderStatsCard(stats, {
diff --git a/api/pin.js b/api/pin.js
index ef14994..4838b0f 100644
--- a/api/pin.js
+++ b/api/pin.js
@@ -58,7 +58,12 @@ export default async (req, res) => {
       cacheSeconds = CONSTANTS.FOUR_HOURS;
     }
 
-    res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
+    res.setHeader(
+      "Cache-Control",
+      `max-age=${
+        cacheSeconds / 2
+      }, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
+    );
 
     return res.send(
       renderRepoCard(repoData, {
diff --git a/api/top-langs.js b/api/top-langs.js
index 1b6b980..d183d3b 100644
--- a/api/top-langs.js
+++ b/api/top-langs.js
@@ -52,7 +52,12 @@ export default async (req, res) => {
       CONSTANTS.ONE_DAY,
     );
 
-    res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
+    res.setHeader(
+      "Cache-Control",
+      `max-age=${
+        cacheSeconds / 2
+      }, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
+    );
 
     return res.send(
       renderTopLanguages(topLangs, {
diff --git a/api/wakatime.js b/api/wakatime.js
index 7680b76..d439c5b 100644
--- a/api/wakatime.js
+++ b/api/wakatime.js
@@ -52,7 +52,12 @@ export default async (req, res) => {
       cacheSeconds = CONSTANTS.FOUR_HOURS;
     }
 
-    res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
+    res.setHeader(
+      "Cache-Control",
+      `max-age=${
+        cacheSeconds / 2
+      }, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
+    );
 
     return res.send(
       renderWakatimeCard(stats, {


Test patch:
diff --git a/tests/api.test.js b/tests/api.test.js
index e183085..0037bcd 100644
--- a/tests/api.test.js
+++ b/tests/api.test.js
@@ -160,7 +160,12 @@ describe("Test /api/", () => {
 
     expect(res.setHeader.mock.calls).toEqual([
       ["Content-Type", "image/svg+xml"],
-      ["Cache-Control", `public, max-age=${CONSTANTS.FOUR_HOURS}`],
+      [
+        "Cache-Control",
+        `max-age=${CONSTANTS.FOUR_HOURS / 2}, s-maxage=${
+          CONSTANTS.FOUR_HOURS
+        }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
+      ],
     ]);
   });
 
@@ -170,7 +175,12 @@ describe("Test /api/", () => {
 
     expect(res.setHeader.mock.calls).toEqual([
       ["Content-Type", "image/svg+xml"],
-      ["Cache-Control", `public, max-age=${15000}`],
+      [
+        "Cache-Control",
+        `max-age=7500, s-maxage=${15000}, stale-while-revalidate=${
+          CONSTANTS.ONE_DAY
+        }`,
+      ],
     ]);
   });
 
@@ -191,7 +201,12 @@ describe("Test /api/", () => {
 
       expect(res.setHeader.mock.calls).toEqual([
         ["Content-Type", "image/svg+xml"],
-        ["Cache-Control", `public, max-age=${CONSTANTS.ONE_DAY}`],
+        [
+          "Cache-Control",
+          `max-age=${CONSTANTS.ONE_DAY / 2}, s-maxage=${
+            CONSTANTS.ONE_DAY
+          }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
+        ],
       ]);
     }
 
@@ -202,7 +217,12 @@ describe("Test /api/", () => {
 
       expect(res.setHeader.mock.calls).toEqual([
         ["Content-Type", "image/svg+xml"],
-        ["Cache-Control", `public, max-age=${CONSTANTS.FOUR_HOURS}`],
+        [
+          "Cache-Control",
+          `max-age=${CONSTANTS.FOUR_HOURS / 2}, s-maxage=${
+            CONSTANTS.FOUR_HOURS
+          }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
+        ],
       ]);
     }
 
@@ -212,7 +232,12 @@ describe("Test /api/", () => {
 
       expect(res.setHeader.mock.calls).toEqual([
         ["Content-Type", "image/svg+xml"],
-        ["Cache-Control", `public, max-age=${CONSTANTS.FOUR_HOURS}`],
+        [
+          "Cache-Control",
+          `max-age=${CONSTANTS.FOUR_HOURS / 2}, s-maxage=${
+            CONSTANTS.FOUR_HOURS
+          }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
+        ],
       ]);
     }
   });