Skip to content

Commit 3bfa2c7

Browse files
committed
Store Nextjs cache on Redis
1 parent 51d2301 commit 3bfa2c7

File tree

4 files changed

+189
-0
lines changed

4 files changed

+189
-0
lines changed

frontend/cache-handler.mjs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { CacheHandler } from "@neshca/cache-handler";
2+
import createLruHandler from "@neshca/cache-handler/local-lru";
3+
import createRedisHandler from "@neshca/cache-handler/redis-stack";
4+
import { createClient } from "redis";
5+
6+
CacheHandler.onCreation(async () => {
7+
let client;
8+
9+
try {
10+
// Create a Redis client.
11+
client = createClient({
12+
url: process.env.REDIS_URL ?? "redis://localhost:6379",
13+
});
14+
15+
// Redis won't work without error handling. https://github.com/redis/node-redis?tab=readme-ov-file#events
16+
client.on("error", (error) => {
17+
if (typeof process.env.NEXT_PRIVATE_DEBUG_CACHE !== "undefined") {
18+
// Use logging with caution in production. Redis will flood your logs. Hide it behind a flag.
19+
console.error("Redis client error:", error);
20+
}
21+
});
22+
} catch (error) {
23+
console.warn("Failed to create Redis client:", error);
24+
}
25+
26+
if (client) {
27+
try {
28+
console.info("Connecting Redis client...");
29+
30+
// Wait for the client to connect.
31+
// Caveat: This will block the server from starting until the client is connected.
32+
// And there is no timeout. Make your own timeout if needed.
33+
await client.connect();
34+
console.info("Redis client connected.");
35+
} catch (error) {
36+
console.warn("Failed to connect Redis client:", error);
37+
38+
console.warn("Disconnecting the Redis client...");
39+
// Try to disconnect the client to stop it from reconnecting.
40+
client
41+
.disconnect()
42+
.then(() => {
43+
console.info("Redis client disconnected.");
44+
})
45+
.catch(() => {
46+
console.warn(
47+
"Failed to quit the Redis client after failing to connect.",
48+
);
49+
});
50+
}
51+
}
52+
53+
/** @type {import("@neshca/cache-handler").Handler | null} */
54+
let handler;
55+
56+
if (client?.isReady) {
57+
// Create the `redis-stack` Handler if the client is available and connected.
58+
handler = await createRedisHandler({
59+
client,
60+
keyPrefix: "nextjs:",
61+
timeoutMs: 1000,
62+
});
63+
} else {
64+
// Fallback to LRU handler if Redis client is not available.
65+
// The application will still work, but the cache will be in memory only and not shared.
66+
handler = createLruHandler();
67+
console.warn(
68+
"Falling back to LRU handler because Redis client is not available.",
69+
);
70+
}
71+
72+
return {
73+
handlers: [handler],
74+
};
75+
});
76+
77+
export default CacheHandler;

frontend/next.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ module.exports = withSentryConfig({
2222
localeDetection: false,
2323
},
2424
trailingSlash: false,
25+
cacheHandler: require.resolve("./cache-handler.js"),
26+
cacheMaxMemorySize: 0, // disable default in-memory caching
2527
async headers() {
2628
return [
2729
{

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@graphql-codegen/typescript": "^2.7.2",
2424
"@graphql-codegen/typescript-operations": "^2.5.2",
2525
"@graphql-codegen/typescript-react-apollo": "^4.3.2",
26+
"@neshca/cache-handler": "^1.9.0",
2627
"@python-italia/pycon-styleguide": "0.1.197",
2728
"@sentry/nextjs": "^8.24.0",
2829
"@vercel/analytics": "^1.1.1",
@@ -56,6 +57,7 @@
5657
"react-use-form-state": "^0.13.2",
5758
"react-use-sync-scroll": "^0.1.0",
5859
"react-wrap-balancer": "^1.1.1",
60+
"redis": "^4.7.0",
5961
"styled-system": "^5.1.5",
6062
"svg-to-pdfkit": "^0.1.8",
6163
"xstate": "^4.38.3",

frontend/pnpm-lock.yaml

Lines changed: 108 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)