forked from dulnan/nuxt-multi-cache
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: move serving of cached route to event handler (dulnan#73)
* feature: move (initial) serving of cached route to event handler * catch errors during serving from cache * write test for using route cache with compression * add doc page for using route cache with compression * also test compression on cached api handlers
- Loading branch information
Showing
18 changed files
with
546 additions
and
300 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Using Route Cache with Compression | ||
|
||
It is possible to use the route cache together with compression, such as with | ||
the [h3-compression](https://github.com/CodeDredd/h3-compression) library. You | ||
may compress both Nuxt-rendered pages or responses from server handlers. | ||
|
||
However, due to the way that both this module and the `h3-compression` library | ||
work, you can not use compression **within the event handler**, for the simple | ||
reason that your event handler is only called once when the response is stored | ||
in cache. Afterwards the cached response is returned immediately. Of course you | ||
can still continue to use compression in an event handler, but just not together | ||
with the route cache. | ||
|
||
For this reason, you have to compress responses globally, via the | ||
[`beforeResponse` Nitro hook](https://nitro.unjs.io/guide/plugins#available-hooks). | ||
This is the only hook that is guaranteed to work; using `render:response` **will | ||
not** work, because this hook is only called on the first render of the page. | ||
|
||
::: info | ||
|
||
While you can use compression from within your app like that, an alternative | ||
approach would be to handle this directly on your web server, using | ||
[mod_deflate for Apache](https://httpd.apache.org/docs/current/mod/mod_deflate.html) | ||
or by setting | ||
[`gzip on` in nginx](https://docs.nginx.com/nginx/admin-guide/web-server/compression/). | ||
|
||
::: | ||
|
||
## Example | ||
|
||
::: code-group | ||
|
||
```typescript [./server/plugins/compression.ts] | ||
import { useCompression } from 'h3-compression' | ||
import { defineNitroPlugin } from 'nitropack/runtime' | ||
|
||
export default defineNitroPlugin((nitro) => { | ||
nitro.hooks.hook('beforeResponse', async (event, response) => { | ||
// Prevent some paths from being compressed. | ||
if (event.path.startsWith('/no-compression')) { | ||
return | ||
} | ||
|
||
await useCompression(event, response) | ||
}) | ||
}) | ||
``` | ||
|
||
::: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<template> | ||
<div> | ||
<h1>This page uses the route cache together with "h3-compression".</h1> | ||
<div id="random-number">{{ random }}</div> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { useRouteCache, useState } from '#imports' | ||
const random = useState('compression_random_number', () => { | ||
return Math.round(Math.random() * 100000000) | ||
}) | ||
useRouteCache((v) => v.setCacheable().setMaxAge(1000)) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { defineEventHandler } from 'h3' | ||
import { useRouteCache } from '#nuxt-multi-cache/composables' | ||
|
||
export default defineEventHandler((event) => { | ||
useRouteCache((helper) => { | ||
helper.setCacheable().setMaxAge(234234) | ||
}, event) | ||
const number = Math.round(Math.random() * 1000000000) | ||
return 'This is a compressed API response: ' + number | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { useCompression } from 'h3-compression' | ||
import { defineNitroPlugin } from 'nitropack/runtime' | ||
|
||
export default defineNitroPlugin((nitro) => { | ||
nitro.hooks.hook('beforeResponse', async (event, response) => { | ||
if ( | ||
!event.path.startsWith('/testCompression') && | ||
!event.path.startsWith('/api/testApiCompression') | ||
) { | ||
return | ||
} | ||
|
||
await useCompression(event, response) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { type H3Event } from 'h3' | ||
import { useMultiCacheApp } from '../utils/useMultiCacheApp' | ||
import { | ||
encodeRouteCacheKey, | ||
getCacheKeyWithPrefix, | ||
getMultiCacheContext, | ||
} from '../../helpers/server' | ||
import { | ||
decodeRouteCacheItem, | ||
handleRawCacheData, | ||
} from '../../helpers/cacheItem' | ||
import { RouteCacheItem } from '../../types' | ||
import { MultiCacheState } from '../../helpers/MultiCacheState' | ||
import { logger } from '../../helpers/logger' | ||
import { setCachedResponse } from '../../helpers/routeCache' | ||
import { useRuntimeConfig } from '#imports' | ||
|
||
function canBeServedFromCache( | ||
key: string, | ||
decoded: RouteCacheItem, | ||
state: MultiCacheState, | ||
): boolean { | ||
const now = Date.now() / 1000 | ||
const isExpired = decoded.expires ? now >= decoded.expires : false | ||
|
||
// Item is not expired, so we can serve it. | ||
if (!isExpired) { | ||
return true | ||
} | ||
|
||
// The route may be served stale while revalidating if it currently is being | ||
// revalidated. | ||
if (decoded.staleWhileRevalidate && state.isBeingRevalidated(key)) { | ||
return true | ||
} | ||
|
||
// Is both expired and not eligible to be served stale while revalidating. | ||
return false | ||
} | ||
|
||
export async function serveCachedHandler(event: H3Event) { | ||
try { | ||
const { serverOptions, state } = useMultiCacheApp() | ||
const context = getMultiCacheContext(event) | ||
|
||
if (!context?.route) { | ||
return | ||
} | ||
|
||
// Build the cache key. | ||
const fullKey = serverOptions?.route?.buildCacheKey | ||
? serverOptions.route.buildCacheKey(event) | ||
: getCacheKeyWithPrefix(encodeRouteCacheKey(event.path), event) | ||
|
||
// Check if there is a cache entry for this key. | ||
const cachedRaw = handleRawCacheData( | ||
await context.route.getItemRaw(fullKey), | ||
) | ||
|
||
// No cache entry. | ||
if (!cachedRaw) { | ||
return | ||
} | ||
const decoded = decodeRouteCacheItem(cachedRaw) | ||
|
||
// Decoding failed. May happen if the format is wrong, possibly after a | ||
// deployment with a newer version. | ||
if (!decoded) { | ||
return | ||
} | ||
|
||
// Store the decoded cache item in the event context. | ||
event.__MULTI_CACHE_DECODED_CACHED_ROUTE = decoded | ||
|
||
// Check if item can be served from cache. | ||
if (!canBeServedFromCache(fullKey, decoded, state)) { | ||
// Mark the key as being revalidated. | ||
if (decoded.staleWhileRevalidate) { | ||
state.addKeyBeingRevalidated(fullKey) | ||
event.__MULTI_CACHE_REVALIDATION_KEY = fullKey | ||
} | ||
|
||
// Returning, so the route is revalidated. | ||
return | ||
} | ||
|
||
const debugEnabled = useRuntimeConfig().multiCache.debug | ||
|
||
if (debugEnabled) { | ||
logger.info('Serving cached route for path: ' + event.path, { | ||
fullKey, | ||
}) | ||
} | ||
|
||
setCachedResponse(event, decoded) | ||
|
||
return decoded.data | ||
} catch (e) { | ||
if (e instanceof Error) { | ||
// eslint-disable-next-line no-console | ||
console.debug(e.message) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.