Skip to content

Commit 135c5e7

Browse files
committed
feat: initial implementation
1 parent 20048dc commit 135c5e7

File tree

10 files changed

+5656
-3234
lines changed

10 files changed

+5656
-3234
lines changed

README.md

Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,37 @@
1-
# Nuxt 3 Minimal Starter
1+
# Netlify Cache Inspector
22

3-
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
3+
Utility web app for inspecting and comparing cache headers for requests to Netlify sites
44

5-
## Setup
5+
## Development
66

7-
Make sure to install the dependencies:
7+
Install dependencies:
88

99
```bash
10-
# npm
11-
npm install
12-
13-
# pnpm
1410
pnpm install
15-
16-
# yarn
17-
yarn install
18-
19-
# bun
20-
bun install
2111
```
2212

23-
## Development Server
13+
### Dev Server
2414

2515
Start the development server on `http://localhost:3000`:
2616

2717
```bash
28-
# npm
29-
npm run dev
30-
31-
# pnpm
3218
pnpm run dev
33-
34-
# yarn
35-
yarn dev
36-
37-
# bun
38-
bun run dev
3919
```
4020

41-
## Production
21+
### Production build
4222

4323
Build the application for production:
4424

4525
```bash
46-
# npm
47-
npm run build
48-
49-
# pnpm
5026
pnpm run build
51-
52-
# yarn
53-
yarn build
54-
55-
# bun
56-
bun run build
5727
```
5828

59-
Locally preview production build:
29+
### Locally preview of a production build
6030

6131
```bash
62-
# npm
63-
npm run preview
64-
65-
# pnpm
6632
pnpm run preview
67-
68-
# yarn
69-
yarn preview
70-
71-
# bun
72-
bun run preview
7333
```
7434

75-
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
35+
## Deployment
36+
37+
This site deploys automatically to Netlify.

app/app.vue

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
11
<template>
2-
<div>
3-
<NuxtRouteAnnouncer />
4-
<NuxtWelcome />
5-
</div>
2+
<NuxtRouteAnnouncer />
3+
4+
<header>
5+
<h1>Netlify Cache Inspector</h1>
6+
7+
<p class="subheading">
8+
Inspect and compare cache headers for requests to Netlify sites
9+
</p>
10+
</header>
11+
12+
<main>
13+
<RequestForm />
14+
</main>
615
</template>
16+
17+
<style>
18+
body {
19+
/* Override weird default from Netlify Examples style */
20+
text-align: left;
21+
}
22+
</style>
23+
<style scoped>
24+
header {
25+
text-align: center;
26+
}
27+
28+
.subheading {
29+
font-size: 1.5em;
30+
}
31+
32+
main {
33+
margin-top: 3em;
34+
}
35+
</style>

app/components/RawCacheHeaders.vue

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script setup lang="ts">
2+
const props = defineProps<{
3+
cacheHeaders: Record<string, string>;
4+
}>();
5+
6+
const id = useId();
7+
const el = useTemplateRef(id);
8+
const highlightJson = () => {
9+
hljs.highlightElement(el.value);
10+
};
11+
onMounted(highlightJson);
12+
onUpdated(highlightJson);
13+
</script>
14+
15+
<template>
16+
<pre>
17+
<code :ref="id" class="hljs language-json">{{ JSON.stringify(props.cacheHeaders, null, 2) }}</code>
18+
</pre>
19+
</template>

app/components/RequestForm.vue

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<script setup lang="ts">
2+
const inputUrl = ref();
3+
const runs = ref([]);
4+
5+
const inspectUrl = async (): void => {
6+
if (!inputUrl.value.startsWith("http")) {
7+
inputUrl.value = `https://${inputUrl.value}`;
8+
}
9+
10+
try {
11+
// Destructuring would be confusing, since the response body contains fields named `status` and
12+
// `headers` (it's a request about a request...)
13+
const responseBody = await $fetch(
14+
`/api/inspect-url/${encodeURIComponent(inputUrl.value)}`,
15+
);
16+
17+
runs.value.push({
18+
url: inputUrl.value,
19+
status: responseBody.status,
20+
cacheHeaders: getCacheHeaders(responseBody.headers),
21+
durationInMs: responseBody.durationInMs,
22+
});
23+
} catch (err) {
24+
// TODO(serhalp) Proper error handling. ErrorBoundary?
25+
console.error("Error fetching URL", err);
26+
return;
27+
}
28+
};
29+
</script>
30+
31+
<template>
32+
<div class="form">
33+
<label class="url-input">
34+
<strong>URL:</strong>
35+
<input v-model.trim="inputUrl" @keyup.enter="inspectUrl()" />
36+
</label>
37+
<button @click="inspectUrl()">Inspect</button>
38+
</div>
39+
40+
<!-- TODO(serhalp) Move this into another component. Wrong place. -->
41+
<div class="flex-btwn">
42+
<div v-for="run in runs">
43+
<h3>{{ run.url }}</h3>
44+
<small>HTTP {{ run.status }} ({{ run.durationInMs }} ms)</small>
45+
<RawCacheHeaders :cacheHeaders="run.cacheHeaders" />
46+
</div>
47+
</div>
48+
</template>
49+
50+
<style scoped>
51+
.form {
52+
display: flex;
53+
gap: 1em;
54+
align-content: center;
55+
align-items: center;
56+
}
57+
58+
label {
59+
display: flex;
60+
gap: 1em;
61+
align-content: center;
62+
align-items: center;
63+
64+
>* {
65+
/* Override default from Netlify Examples style to vertically center easily */
66+
margin-bottom: 0;
67+
}
68+
}
69+
70+
.url-input {
71+
flex: 1;
72+
}
73+
</style>

app/utils/getCacheHeaders.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const CACHE_HEADER_NAMES = [
2+
"Age",
3+
"CDN-Cache-Control",
4+
"Cache-Control",
5+
"Cache-Status",
6+
"Content-Encoding",
7+
"Content-Type",
8+
"Date",
9+
"ETag",
10+
"Netlify-CDN-Cache-Control",
11+
"Vary",
12+
"X-BB-Deploy-Id",
13+
"X-BB-Gen",
14+
"X-NF-Cache-Info",
15+
"X-NF-Cache-Result",
16+
];
17+
18+
export default function getCacheHeaders(headersObj: Record<string, string>) {
19+
// Use a Headers instance for case insensitivity and multi-value handling
20+
const headers = new Headers(headersObj);
21+
22+
// I'd rather avoid mutations here but it's really awkward to handle nil values otherwise
23+
const cacheHeaders = new Headers();
24+
for (const name of CACHE_HEADER_NAMES) {
25+
const val = headers.get(name);
26+
if (val != null) cacheHeaders.set(name, val);
27+
}
28+
29+
return Object.fromEntries(cacheHeaders);
30+
}

eslint.config.mjs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// @ts-check
2+
import withNuxt from "./.nuxt/eslint.config.mjs";
3+
4+
export default withNuxt().override("nuxt/vue", {
5+
rules: {
6+
"vue/html-self-closing": [
7+
"error",
8+
{
9+
html: {
10+
// The default config here isn't compatible with prettier:
11+
// https://github.com/vuejs/eslint-plugin-vue/issues/2232.
12+
void: "always",
13+
},
14+
},
15+
],
16+
},
17+
});

nuxt.config.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,51 @@ export default defineNuxtConfig({
33
compatibilityDate: "2024-09-13",
44
future: { compatibilityVersion: 4 },
55
devtools: { enabled: true },
6+
7+
app: {
8+
head: {
9+
link: [
10+
// See https://example-styles.netlify.app/.
11+
{
12+
rel: "preload",
13+
href: "https://example-styles.netlify.app/fonts/PacaembuVar-latin.woff2",
14+
as: "font",
15+
type: "font/woff2",
16+
crossorigin: "",
17+
},
18+
{
19+
rel: "preload",
20+
href: "https://example-styles.netlify.app/fonts/MulishVar-latin.woff2",
21+
as: "font",
22+
type: "font/woff2",
23+
crossorigin: "",
24+
},
25+
{
26+
rel: "stylesheet",
27+
href: "https://example-styles.netlify.app/styles.css",
28+
},
29+
{
30+
rel: "stylesheet",
31+
href: "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@latest/build/styles/default.min.css",
32+
},
33+
],
34+
script: [
35+
{
36+
src: "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@latest/build/highlight.min.js",
37+
},
38+
// TODO(serhalp) Check if this is really the idiomatic way to do this...
39+
{
40+
children: "hljs.highlightAll();",
41+
},
42+
],
43+
},
44+
},
45+
46+
routeRules: {
47+
"/": {
48+
prerender: true,
49+
},
50+
},
51+
52+
modules: ["@nuxt/eslint"],
653
});

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
{
2-
"name": "nuxt-app",
2+
"name": "netlify-cache-inspector",
3+
"description": "Inspect and compare cache headers for requests to Netlify sites",
34
"private": true,
45
"type": "module",
56
"scripts": {
67
"build": "nuxt build",
78
"dev": "nuxt dev",
89
"generate": "nuxt generate",
910
"preview": "nuxt preview",
10-
"postinstall": "nuxt prepare"
11+
"postinstall": "nuxt prepare",
12+
"typecheck": "npx nuxi typecheck"
1113
},
1214
"dependencies": {
15+
"@nuxt/eslint": "^0.5.7",
1316
"nuxt": "^3.13.0",
1417
"vue": "latest",
1518
"vue-router": "latest"
19+
},
20+
"devDependencies": {
21+
"typescript": "^5.6.2",
22+
"vue-tsc": "^2.1.6"
1623
}
1724
}

0 commit comments

Comments
 (0)