Skip to content

Commit 3df80d9

Browse files
committed
First commit
0 parents  commit 3df80d9

File tree

9 files changed

+388
-0
lines changed

9 files changed

+388
-0
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Local Netlify folder
2+
.netlify

Diff for: README.md

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Cache-key variations on Netlify
2+
3+
This is a demo of using `Netlify-Vary` on Netlify Edge, with an example of how
4+
to take advantage of Netlify's cache-key variations feature to customise
5+
how your dynamic content is cached on Netlify's CDN.
6+
7+
Visit [the demo site](https://cache-key-variations.netlify.app/) to see this in action.
8+
9+
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/netlify-labs/cache-key-variations)
10+
11+
Netlify now supports a custom `Netlify-Vary` header, which instructs Netlify’s edge
12+
on how to better cache and serve dynamic assets using Netlify’s Edge Cache with
13+
Netlify Functions or external services proxied to using redirect rules, giving
14+
fine grained control over which parts of a request need to match the cached object.
15+
16+
## How do cache-key variations work?
17+
18+
`Netlify-Vary` takes a set of comma delimited instructions for what parts of the request to vary on:
19+
20+
- `query` vary by request URL query parameters
21+
- `header` vary by the value of a request header
22+
- `language` vary by the languages from the `Accept-Language` request header
23+
- `country` vary by the country inferred from doing the GeoIP lookup on the request IP address
24+
- `cookie` vary by a value of a request cookie
25+
26+
These instructions, together with the request URL, will define the cache key which Netlify uses to uniquely identify a cached object in our CDN.
27+
28+
Here’s an example of a Function that supports the product catalog for an e-commerce site, and will be cached on Netlify Edge. Its cache-key will vary depending on:
29+
- where the client is located (so different countries can have different product offerings)
30+
- the `productType` requested on the incoming request URL query parameter (so
31+
other query parameters such as `_utm` or session identifiers for analytics
32+
don't affect the cache hit-rate for this content)
33+
34+
```jsx
35+
import type { Handler, HandlerEvent, HandlerContext } from "@netlify/functions";
36+
37+
const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
38+
const productType: string = event.queryStringParameters?.productType;
39+
const resp = await fetch('https://your-api.com/catalog?productType='+productType)
40+
const json = await resp.json()
41+
42+
const headers = {
43+
"Content-Type": "text/html",
44+
"Cache-Control": "public, max-age=0, must-revalidate", // Tell browsers to always revalidate
45+
"Netlify-CDN-Cache-Control": "public, max-age=31536000, must-revalidate", // Tell Edge to cache asset for up to a year
46+
"Netlify-Vary": "query=productType,header=X-Country"
47+
}
48+
49+
return {
50+
statusCode: 200,
51+
body: `<!doctype html><html>
52+
<head>
53+
<title>E-commerce catalog</title>
54+
</head>
55+
<body>
56+
<h2>Catalog</h2>
57+
<ul>${json.map((item) => `<li>${item.title}</li>`).join("\n")}</ul>
58+
</body><html>`,
59+
headers
60+
}
61+
};
62+
63+
export { handler };
64+
```
65+
66+
In this example, different cache objects are created for different matches to
67+
the instructions, and a single additional cache object is created for all
68+
non-matches. For example, the content from a request to `yoursite.com/catalog?productType=clothes`
69+
and a request to `yoursite.com/catalog` (no query parameter) will have separate
70+
cache-keys, but requests to `yoursite.com/catalog` and
71+
`yoursite.com/catalog?otherParam=something` will have the same cache-key.

Diff for: netlify.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
publish = "public"

Diff for: netlify/edge-functions/inner.ts

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import type { Context, Config } from "@netlify/edge-functions";
2+
3+
export default async (request: Request, context: Context) => {
4+
// let requests for other assets (such as .css) passthrough
5+
const url = new URL(request.url);
6+
if (url.pathname !== "/") {
7+
return;
8+
}
9+
10+
const guessStr = url.searchParams.get("value");
11+
var guessContent = ``;
12+
if (guessStr === "too_low") {
13+
guessContent = `<p><strong>You guessed too low &#128532; Try again!</strong></p>`
14+
} else if (guessStr === "too_high") {
15+
guessContent = `<p><strong>You guessed too high &#128532; Try again!</strong></p>`
16+
} else if (guessStr === "correct") {
17+
guessContent = `<p><strong>You guessed correctly! &#128513;</strong></p>`
18+
}
19+
20+
const cookiePayload = context.cookies.get("visited") === "true" ?
21+
`this is not the first time` :
22+
`this is the first time`;
23+
24+
// always set cookie so we know you've visited this page
25+
context.cookies.set("visited", "true");
26+
27+
const countryName = context.geo?.country?.name || "unknown";
28+
29+
const body = `<!DOCTYPE html>
30+
<html>
31+
<head>
32+
<title>Cache-key variations on Netlify</title>
33+
<link rel="stylesheet" href="/main.css">
34+
</head>
35+
<body>
36+
<div class="wrapper">
37+
<h1>Cache-key variations on Netlify</h1>
38+
39+
<p>Cache-key variations are a powerful new tool in your arsenal.
40+
All the content you'll see on this page is served under the root URL with the help of
41+
<strong>Netlify Edge Functions</strong> and <strong>Cache-key variations</strong>!</p>
42+
43+
<p>This page in particular was generated at <strong>${new Date}</strong>.</p>
44+
45+
<p>You'll see different timestamps depending on:</p>
46+
47+
<ul>
48+
<li><p>Whether it's your <strong>first time visiting this site</strong>. We know that <strong>${cookiePayload}</strong>
49+
you're visiting this site because we're setting a cookie on your browser (sorry), and we're <strong>
50+
varying the content based on one of the keys of that cookie</strong>.</p></li>
51+
52+
<li><p><strong>Where you're located</strong>. We know that you're most likely from ${countryName} because Netlify does geolocation
53+
based on your IP address, and we're also <strong>varying content based on geolocation</strong>, so if that timestamp looks
54+
recent you're probably the first person in your country to see this page.</p></li>
55+
56+
<li><p>Your <strong>answer to the question at the end of this page</strong>. Your answer is submitted as query parameter which
57+
we <strong>validate using another edge-function and use to vary this content.</strong></p></li>
58+
</ul>
59+
60+
${guessContent}
61+
62+
<p><strong>How deep is the deepest point of the <a href="https://en.wikipedia.org/wiki/Mariana_Trench">Mariana Trench<strong></a>?</p>
63+
64+
<form action="/guess" method="get">
65+
<input type="number" id="value" name="value"><br><br>
66+
<input type="submit" value="Submit">
67+
</form>
68+
<br>
69+
70+
<p>Try reloading this page a couple of times and see how the content is served from the cache!</p>
71+
</div>
72+
</body>
73+
</html>`;
74+
75+
76+
return new Response(body, {
77+
status: 200,
78+
headers: {
79+
"Content-Type": "text/html",
80+
"Cdn-Cache-Control": "public, s-maxage=31536000, must-revalidate",
81+
"Cache-Control": "public, max-age=0, must-revalidate",
82+
"Netlify-Vary": "cookie=visited,header=X-Country,query=value"
83+
}
84+
});
85+
};
86+
87+
export const config: Config = {
88+
cache: "manual",
89+
path: "/*"
90+
}

Diff for: netlify/edge-functions/outer.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { Context, Config } from "@netlify/edge-functions";
2+
3+
const correctGuess = 11034;
4+
5+
export default async (request: Request, context: Context) => {
6+
// Only transform query on guess submission
7+
const url = new URL(request.url);
8+
const guessStr = url.searchParams.get("value");
9+
if (url.pathname !== "/guess") {
10+
return context.next();
11+
}
12+
13+
const newURL = new URL("/", request.url);
14+
// Look for the query parameter and rewrite the form answer
15+
// into something with lower cardinality
16+
const guess = +guessStr;
17+
const guessParams = new URLSearchParams();
18+
if (guess < correctGuess) {
19+
guessParams.set("value", "too_low");
20+
} else if (guess > correctGuess) {
21+
guessParams.set("value", "too_high");
22+
} else {
23+
guessParams.set("value", "correct");
24+
}
25+
newURL.search = guessParams.toString();
26+
27+
return newURL;
28+
};
29+
30+
export const config: Config = {
31+
path: "/*"
32+
}

Diff for: package-lock.json

+56
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "swr-tests",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [],
10+
"author": "",
11+
"license": "ISC",
12+
"dependencies": {
13+
"@netlify/functions": "^2.3.0"
14+
},
15+
"devDependencies": {
16+
"@types/node": "^20.7.0"
17+
}
18+
}

Diff for: public/index.html

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Cache-key variations on Netlify</title>
5+
<link rel="stylesheet" href="/main.css">
6+
</head>
7+
<body>
8+
<div class="wrapper">
9+
<h1>Cache-key variations on Netlify</h1>
10+
11+
<p>Cache-key variations are a powerful new tool in your arsenal.
12+
This entire page is served on the root URL with the help of
13+
cached Netlify Edge Functions and Cache-key variations.</p>
14+
15+
<p><strong>This response in particular was not served from our
16+
cache.</strong></p>
17+
18+
<p>It's seems that this is the first time you've visited this site.
19+
We know this because we're setting a cookie on your browser (sorry),
20+
and we're varying the content based on one of the keys of that cookie!</p>
21+
22+
<p>It also seems like you're the first person from <Country> to
23+
visit our site! We know this because Netlify does geolocation based
24+
off your IP address, and we're also varying based on geolocation!</p>
25+
26+
<p>How about we play a game?</p>
27+
28+
<p>How many kilometers deep do you think it is the Mariana Trench?</p>
29+
30+
<div class="purge_buttons">
31+
<form action="/" method="get">
32+
<label for="guess">Guess:</label>
33+
<input type="number" id="guess" name="guess"><br><br>
34+
<input type="submit" value="Submit">
35+
</form>
36+
</div>
37+
38+
<p>Try reloading this page a couple of times and see how the
39+
response eventually is served from cache directly!</p>
40+
</div>
41+
</body>
42+
</html>

Diff for: public/main.css

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* Reset some default browser styles */
2+
body, h1, p, ul, li {
3+
margin: 0;
4+
padding: 0;
5+
}
6+
7+
body {
8+
font-family: Pacaembu, -apple-system, "system-ui", "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
9+
font-size: 16px;
10+
line-height: 1.5;
11+
color: #333;
12+
background-color: #fefefe;
13+
margin: 2em auto;
14+
max-width: 40em; /* Control the body width */
15+
}
16+
17+
h1 {
18+
font-size: 2.5em;
19+
margin-bottom: 0.5em;
20+
color: #2c3e50;
21+
}
22+
23+
p, ul, li {
24+
margin-bottom: 1em;
25+
color: #555;
26+
}
27+
28+
ul {
29+
list-style-type: disc;
30+
padding-left: 1.5em;
31+
}
32+
33+
input[type=submit] {
34+
background-color: #2c3e50;
35+
border: none;
36+
border-radius: 4px;
37+
color: #fefefe;
38+
padding: 10px 25px;
39+
text-align: center;
40+
text-decoration: none;
41+
display: inline-block;
42+
font-size: 16px;
43+
transition-duration: 0.4s;
44+
margin-left: 16px;
45+
}
46+
47+
form {
48+
display: flex;
49+
justify-content: flex-start;
50+
}
51+
52+
input:hover {
53+
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
54+
}
55+
56+
/* Responsive design to adjust typography */
57+
@media (max-width: 768px) {
58+
body {
59+
font-size: 15px;
60+
}
61+
62+
h1 {
63+
font-size: 2em;
64+
}
65+
}
66+
67+
@media (max-width: 480px) {
68+
body {
69+
font-size: 14px;
70+
}
71+
72+
h1 {
73+
font-size: 1.75em;
74+
}
75+
}

0 commit comments

Comments
 (0)