Skip to content

Commit e8745d0

Browse files
committed
feat: add bypass cache feature for perf debuggability
Here it is in action: ``` ❯ time -p curl -s -I "http://127.0.0.1:3000/vue-use" | grep x-cache real 5.34 ❯ time -p curl -s -I "http://127.0.0.1:3000/vue-use" | grep x-cache real 0.37 ❯ time -p curl -s -I "http://127.0.0.1:3000/vue-use?__bypass_cache__=1" | grep x-cache x-cache-bypass: all real 2.55 ❯ time -p curl -s -I "http://127.0.0.1:3000/vue-use" | grep x-cache real 0.35 ❯ time -p curl -s -I "http://127.0.0.1:3000/vue-use?__bypass_cache__=npm-package" | grep x-cache x-cache-bypass: npm-package real 0.48 ```
1 parent f824e77 commit e8745d0

24 files changed

Lines changed: 509 additions & 49 deletions

File tree

CONTRIBUTING.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,68 @@ pnpm test:nuxt # Nuxt component tests
6464
pnpm test:browser # Playwright E2E tests
6565
```
6666

67+
### Debugging cache
68+
69+
The app has multiple caching layers that can make debugging tricky. You can bypass caching using the `__bypass_cache__` query parameter.
70+
71+
**Category bypass** (bypasses all caches of that type):
72+
73+
```
74+
?__bypass_cache__=1 # Bypass ALL cache layers
75+
?__bypass_cache__=all # Same as above
76+
?__bypass_cache__=fetch # Bypass external API fetch cache (npm registry, JSR, GitHub, etc.)
77+
?__bypass_cache__=handler # Bypass all API route handlers
78+
```
79+
80+
**Specific key bypass** (bypasses only that cache):
81+
82+
```
83+
?__bypass_cache__=readme # Bypass only the readme handler
84+
?__bypass_cache__=npm-package # Bypass only npm package fetching
85+
?__bypass_cache__=readme,fetch # Combine specific keys with categories
86+
```
87+
88+
<details>
89+
<summary>All available bypass keys</summary>
90+
91+
| Key | What it caches |
92+
| ------------------- | ------------------------------------------------------------- |
93+
| **Categories** | |
94+
| `fetch` | External API calls (npm registry, JSR, GitHub, etc.) with SWR |
95+
| `handler` | All API route handlers |
96+
| **API Routes** | |
97+
| `readme` | Package README content |
98+
| `analysis` | Package analysis (types, create-\*, etc.) |
99+
| `docs` | Generated TypeScript docs |
100+
| `files` | Package file tree |
101+
| `file` | Individual file content |
102+
| `vulnerabilities` | Dependency vulnerability scan |
103+
| `badge` | Version badge SVG |
104+
| `install-size` | Install size calculation handler |
105+
| `org-packages` | Org package list |
106+
| `jsr` | JSR package lookup handler |
107+
| `suggestions` | Search suggestions |
108+
| `contributors` | GitHub contributors |
109+
| `skills` | Package skills |
110+
| `well-known-skills` | .well-known/skills endpoints |
111+
| **Utilities** | |
112+
| `npm-package` | npm packument fetching |
113+
| `jsr-package` | JSR package info fetching |
114+
| `install-size-calc` | Install size calculation |
115+
116+
</details>
117+
118+
> [!TIP]
119+
> To bypass **all** caching layers including Vercel's CDN, use a unique value like a timestamp: `?__bypass_cache__=1738438123`. The CDN caches based on the full URL, so a unique query string causes a cache miss at the edge, and then our code bypasses the internal caches too.
120+
121+
When cache bypass is active, the response includes an `X-Cache-Bypass` header showing which caches were bypassed:
122+
123+
```
124+
X-Cache-Bypass: readme,npm-package
125+
```
126+
127+
In development mode, cache bypass also logs to the console.
128+
67129
### Project structure
68130

69131
```

server/api/contributors.get.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface GitHubContributor {
66
contributions: number
77
}
88

9-
export default defineCachedEventHandler(
9+
export default defineBypassableCachedEventHandler(
1010
async (): Promise<GitHubContributor[]> => {
1111
const allContributors: GitHubContributor[] = []
1212
let page = 1
@@ -52,6 +52,7 @@ export default defineCachedEventHandler(
5252
{
5353
maxAge: 3600, // Cache for 1 hour
5454
name: 'github-contributors',
55+
bypassKey: 'contributors',
5556
getKey: () => 'contributors',
5657
},
5758
)

server/api/jsr/[...pkg].get.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { JsrPackageInfo } from '#shared/types/jsr'
1111
* @example GET /api/jsr/@std/fs → { exists: true, scope: "std", name: "fs", ... }
1212
* @example GET /api/jsr/lodash → { exists: false }
1313
*/
14-
export default defineCachedEventHandler<Promise<JsrPackageInfo>>(
14+
export default defineBypassableCachedEventHandler<Promise<JsrPackageInfo>>(
1515
async event => {
1616
const pkgPath = getRouterParam(event, 'pkg')
1717

@@ -30,6 +30,7 @@ export default defineCachedEventHandler<Promise<JsrPackageInfo>>(
3030
maxAge: CACHE_MAX_AGE_ONE_HOUR,
3131
swr: true,
3232
name: 'api-jsr-package',
33+
bypassKey: 'jsr',
3334
getKey: event => {
3435
const pkg = getRouterParam(event, 'pkg') ?? ''
3536
return `jsr:v1:${pkg.replace(/\/+$/, '').trim()}`

server/api/opensearch/suggestions.get.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as v from 'valibot'
22
import { SearchQuerySchema } from '#shared/schemas/package'
33
import { CACHE_MAX_AGE_ONE_MINUTE, NPM_REGISTRY } from '#shared/utils/constants'
44

5-
export default defineCachedEventHandler(
5+
export default defineBypassableCachedEventHandler(
66
async event => {
77
const query = getQuery(event)
88

@@ -28,6 +28,7 @@ export default defineCachedEventHandler(
2828
{
2929
maxAge: CACHE_MAX_AGE_ONE_MINUTE,
3030
swr: true,
31+
bypassKey: 'suggestions',
3132
getKey: event => {
3233
const query = getQuery(event)
3334
const q = String(query.q || '').trim()

server/api/registry/analysis/[...pkg].get.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import { parseRepoUrl } from '#shared/utils/git-providers'
2121
import { getLatestVersion, getLatestVersionBatch } from 'fast-npm-meta'
2222

23-
export default defineCachedEventHandler(
23+
export default defineBypassableCachedEventHandler(
2424
async event => {
2525
// Parse package name and optional version from path
2626
// e.g., "vue" or "vue/v/3.4.0" or "@nuxt/kit" or "@nuxt/kit/v/1.0.0"
@@ -69,6 +69,7 @@ export default defineCachedEventHandler(
6969
{
7070
maxAge: CACHE_MAX_AGE_ONE_DAY, // 24 hours - analysis rarely changes
7171
swr: true,
72+
bypassKey: 'analysis',
7273
getKey: event => {
7374
const pkg = getRouterParam(event, 'pkg') ?? ''
7475
return `analysis:v1:${pkg.replace(/\/+$/, '').trim()}`

server/api/registry/badge/[...pkg].get.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function measureTextWidth(text: string, charWidth = 6.2, paddingX = 6): number {
1010
return Math.max(40, Math.round(text.length * charWidth) + paddingX * 2)
1111
}
1212

13-
export default defineCachedEventHandler(
13+
export default defineBypassableCachedEventHandler(
1414
async event => {
1515
const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? []
1616
if (pkgParamSegments.length === 0) {
@@ -67,6 +67,7 @@ export default defineCachedEventHandler(
6767
{
6868
maxAge: CACHE_MAX_AGE_ONE_HOUR,
6969
swr: true,
70+
bypassKey: 'badge',
7071
getKey: event => {
7172
const pkg = getRouterParam(event, 'pkg') ?? ''
7273
return `badge:version:${pkg}`

server/api/registry/docs/[...pkg].get.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { assertValidPackageName } from '#shared/utils/npm'
33
import { parsePackageParam } from '#shared/utils/parse-package-param'
44
import { generateDocsWithDeno } from '#server/utils/docs'
55

6-
export default defineCachedEventHandler(
6+
export default defineBypassableCachedEventHandler(
77
async event => {
88
const pkgParam = getRouterParam(event, 'pkg')
99
if (!pkgParam) {
@@ -62,6 +62,7 @@ export default defineCachedEventHandler(
6262
{
6363
maxAge: 60 * 60, // 1 hour cache
6464
swr: true,
65+
bypassKey: 'docs',
6566
getKey: event => {
6667
const pkg = getRouterParam(event, 'pkg') ?? ''
6768
return `docs:v2:${pkg}`

server/api/registry/file/[...pkg].get.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ async function fetchFileContent(
9393
* - /api/registry/file/packageName/v/1.2.3/path/to/file.ts
9494
* - /api/registry/file/@scope/packageName/v/1.2.3/path/to/file.ts
9595
*/
96-
export default defineCachedEventHandler(
96+
export default defineBypassableCachedEventHandler(
9797
async event => {
9898
// Parse: [pkg, 'v', version, ...filePath] or [@scope, pkg, 'v', version, ...filePath]
9999
const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? []
@@ -200,6 +200,7 @@ export default defineCachedEventHandler(
200200
{
201201
// File content for a specific version never changes - cache permanently
202202
maxAge: CACHE_MAX_AGE_ONE_YEAR, // 1 year
203+
bypassKey: 'file',
203204
getKey: event => {
204205
const pkg = getRouterParam(event, 'pkg') ?? ''
205206
return `file:v${CACHE_VERSION}:${pkg.replace(/\/+$/, '').trim()}`

server/api/registry/files/[...pkg].get.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { CACHE_MAX_AGE_ONE_YEAR, ERROR_FILE_LIST_FETCH_FAILED } from '#shared/ut
1010
* - /api/registry/files/packageName/v/1.2.3 - required version
1111
* - /api/registry/files/@scope/packageName/v/1.2.3 - scoped package
1212
*/
13-
export default defineCachedEventHandler(
13+
export default defineBypassableCachedEventHandler(
1414
async event => {
1515
// Parse package name and version from URL segments
1616
// Patterns: [pkg, 'v', version] or [@scope, pkg, 'v', version]
@@ -44,6 +44,7 @@ export default defineCachedEventHandler(
4444
// Files for a specific version never change - cache permanently
4545
maxAge: CACHE_MAX_AGE_ONE_YEAR, // 1 year
4646
swr: true,
47+
bypassKey: 'files',
4748
getKey: event => {
4849
const pkg = getRouterParam(event, 'pkg') ?? ''
4950
return `files:v1:${pkg.replace(/\/+$/, '').trim()}`

server/api/registry/install-size/[...pkg].get.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { CACHE_MAX_AGE_ONE_HOUR, ERROR_CALC_INSTALL_SIZE_FAILED } from '#shared/
88
* Calculate total install size for a package including all dependencies.
99
* Handles platform-specific optional dependencies by counting only one representative per group.
1010
*/
11-
export default defineCachedEventHandler(
11+
export default defineBypassableCachedEventHandler(
1212
async event => {
1313
// Parse package name and optional version from path segments
1414
// Supports: /install-size/lodash, /install-size/lodash/v/4.17.21, /install-size/@scope/name, /install-size/@scope/name/v/1.0.0
@@ -46,6 +46,7 @@ export default defineCachedEventHandler(
4646
{
4747
maxAge: CACHE_MAX_AGE_ONE_HOUR,
4848
swr: true,
49+
bypassKey: 'install-size',
4950
getKey: event => {
5051
const pkg = getRouterParam(event, 'pkg') ?? ''
5152
return `install-size:v1:${pkg.replace(/\/+$/, '').trim()}`

0 commit comments

Comments
 (0)