Redis-Caching Implementeren voor Next.js 15-Apps
Redis-Caching Implementeren voor Next.js 15-Apps
Next.js 15 introduceert standaard agressieve caching-strategieën, maar uitsluitend vertrouwen op de ingebouwde bestandssysteemcache (file-system cache) of geheugencache (memory cache) voor enterprise-applicaties is een recept voor verouderde gegevens, onvoorspelbare prestaties en problemen bij het deployen. Wanneer je schaalt over meerdere serverless functies of containers, schiet lokale caching tekort. Je hebt een gedistribueerde cache nodig. Je hebt Redis nodig.
Deze gids behandelt de precieze implementatie van Redis-caching voor Next.js 15-apps. We bouwen een robuuste, schaalbare architectuur met behulp van Upstash Redis (of een andere Redis-provider) en de native Next.js unstable_cache API, waarmee we de fragiele standaardinstellingen omzeilen en de volledige controle over onze datalaag overnemen.
Het Probleem
Next.js 15 leunt zwaar op de Fetch API cache en route segment caching. Standaard slaat het gecachte gegevens op in het lokale bestandssysteem. Dit werkt prima voor een statische site die op een enkele Node.js-instantie draait.
Maar wanneer je deployt naar serverless platforms zoals Vercel of AWS Lambda, schaalt je applicatie op door meerdere onafhankelijke instanties op te starten. Instantie A heeft geen toegang tot de bestandssysteemcache van Instantie B. Als een gebruiker Instantie A raakt, krijgt hij mogelijk verse gegevens. Als hij Instantie B raakt, krijgt hij mogelijk verouderde gegevens of triggert hij een overbodige databasequery.
Bovendien is caching op het bestandssysteem vluchtig. Het implementeren van een nieuwe versie van je app wist vaak de volledige cache, wat onmiddellijk na een deployment leidt tot enorme verkeerspieken op je database (een cache stampede).
We hebben een persistente, gedistribueerde cache nodig die buiten de applicatiecode staat en deployments overleeft.
Waarom Het Moeilijk Is
Het integreren van Redis in Next.js is meer dan alleen het aanroepen van redis.get() en redis.set(). Next.js 15 maakt gebruik van een uitgesproken React Server Components (RSC) architectuur. Het framework wil de cachinglaag het liefst zelf beheren.
Als je je database-aanroepen simpelweg in Redis-commando's verpakt, werk je het framework tegen. Je verliest de voordelen van on-demand revalidatie (revalidateTag, revalidatePath), en je riskeert dat er inconsistente gegevens naar de client worden gestuurd.
De uitdaging ligt in het injecteren van Redis in de caching-levenscyclus van Next.js. We moeten de cache-lees- en schrijfbewerkingen van het framework onderscheppen en de vluchtige bestandssysteemcache naadloos vervangen door onze gedistribueerde Redis-store, terwijl we compatibel blijven met de revalidatie-primitives van Next.js.
Dit vereist aangepaste cache-handlers, een functie waarvan de experimentele ondersteuning in Next.js in wisselende staten verkeerde. In Next.js 15 is het configureren van een custom cache-handler de enige acceptabele weg voor applicaties met veel verkeer.
De Architectuur
Onze architectuur bestaat uit drie lagen:
- De Applicatielaag (Next.js 15): React Server Components die de bedrijfslogica uitvoeren en de UI renderen.
- De Cache-interceptor: Een aangepaste Next.js cache-handler die
unstable_cacheenfetch-verzoeken onderschept. - De Gedistribueerde Cachelaag (Redis): Een snelle, in-memory key-value store die de geserialiseerde antwoorden bevat.
Wanneer een Server Component om gegevens vraagt:
- Het framework roept onze aangepaste cache-handler aan.
- De handler controleert Redis op de betreffende sleutel.
- Als er een cache-hit is, deserialiseren we de JSON en retourneren we deze. Het framework rendert direct de UI.
- Als er een cache-miss is, voert het framework de data-fetching logica uit (bijv. PostgreSQL bevragen), geeft het resultaat door aan onze handler, die dit naar Redis schrijft voordat het naar de component wordt teruggestuurd.
Dit zorgt ervoor dat alle serverless instanties één enkele bron van waarheid (single source of truth) delen.
Implementatie
We gebruiken het @neshca/cache-handler pakket. Dit biedt een robuuste, productierijpe basis voor custom cache-handlers in Next.js, specifiek ontworpen om verbinding te maken met Redis. We gebruiken ioredis als onze Redis-client.
1. Installeer Afhankelijkheden
npm install @neshca/cache-handler ioredis
2. Configureer de Redis-client
Maak een robuuste Redis-client-instantie aan. Zorg ervoor dat er niet meerdere verbindingen per serverless aanroep worden geïnitieerd.
// lib/redis.ts
import { Redis } from 'ioredis';
const redisUrl = process.env.REDIS_URL;
if (!redisUrl) {
throw new Error('REDIS_URL environment variable is not defined');
}
// Zorg voor een enkele instantie in development om verbindingslekken tijdens HMR te voorkomen
const globalForRedis = global as unknown as { redis: Redis };
export const redis = globalForRedis.redis || new Redis(redisUrl, {
maxRetriesPerRequest: 3,
enableReadyCheck: false,
});
if (process.env.NODE_ENV !== 'production') globalForRedis.redis = redis;
3. Maak de Cache-handler aan
Dit bestand vertelt Next.js hoe het met Redis moet communiceren. Het koppelt Next.js cache-operaties (get, set, revalidateTag) aan Redis-commando's.
// cache-handler.mjs
import { CacheHandler } from '@neshca/cache-handler';
import createRedisHandler from '@neshca/cache-handler/redis-strings';
import { Redis } from 'ioredis';
CacheHandler.onCreation(async () => {
let client;
try {
// We instantiëren de client hier.
// Zorg ervoor dat REDIS_URL is ingesteld in productie.
client = new Redis(process.env.REDIS_URL, {
maxRetriesPerRequest: 3,
lazyConnect: true, // Blokkeer het opstarten niet
});
// Test de verbinding
client.on('error', (error) => {
console.error('Redis-verbindingsfout:', error);
});
} catch (error) {
console.warn('Kan de Redis-client niet initialiseren voor de cache-handler', error);
}
return {
handlers: [
createRedisHandler({
client,
keyPrefix: 'next-cache:',
timeoutMs: 1000, // Faal snel als Redis traag is
}),
],
};
});
export default CacheHandler;
4. Koppel het aan Next.js
Vertel Next.js om je aangepaste cache-handler te gebruiken in next.config.js.
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
// Opmerking: experimentele functies veranderen, maar dit is het huidige patroon voor Next 15
cacheHandler: require.resolve('./cache-handler.mjs'),
cacheLife: {
default: {
stale: 3600, // 1 uur
revalidate: 86400, // 1 dag
},
},
},
};
module.exports = nextConfig;
5. Gegevens Ophalen
Gebruik nu unstable_cache of native fetch zoals je gewend bent. Het framework leidt de caching ongemerkt via Redis.
// app/products/[id]/page.tsx
import { unstable_cache } from 'next/cache';
import { db } from '@/lib/db';
const getProduct = unstable_cache(
async (id: string) => {
console.log('Cache miss: Product ophalen uit DB', id);
return await db.product.findUnique({ where: { id } });
},
['product-details'], // Cache key-segmenten
{ tags: ['products'], revalidate: 3600 }
);
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id);
if (!product) return <div>Product niet gevonden</div>;
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
</main>
);
}
Wanneer je deze gegevens moet invalideren (bijv. via een webhook van een beheerpaneel), roep je simpelweg revalidateTag('products') aan. De cache-handler vertaalt dit in een Redis DEL of tag-gebaseerde invalidatie, zodat het volgende verzoek direct de database raakt.
Valkuilen
Het implementeren van Redis-caching in Next.js 15 legt enkele architectonische valkuilen bloot. Vermijd deze problemen.
1. Redis Latentie en Timeout Storms
Redis is snel, maar netwerklatentie is onvoorspelbaar. Als je Redis-instantie uitvalt, of de verbindingslatentie piekt naar 2000ms, zal je hele Next.js-app vastlopen in afwachting van de cache.
Oplossing: Dwing strikte timeouts af in je cache-handler (bijv. timeoutMs: 500). Als Redis niet binnen 500ms reageert, moet de cache-handler dit geruisloos afhandelen als een cache-miss en terugvallen op de database. Beschikbaarheid is belangrijker dan prestaties.
2. Serialisatie van Grote Objecten
Redis slaat strings op. Next.js serialiseert je gegevens naar JSON voordat ze naar de cache-handler worden gestuurd. Het opslaan van gigantische JSON-blobs van 5MB (zoals een hele ongepagineerde tabel) zal je netwerk-I/O van Redis belasten en veel CPU-cycli kosten bij het serialiseren en deserialiseren.
Oplossing: Cache alleen wat nodig is. Projecteer je databasequeries. Vraag geen SELECT * aan, maar haal alleen de velden op die de component daadwerkelijk nodig heeft om de payload te verkleinen.
3. Cache Stampedes
Wanneer een veelgebruikte cachesleutel verloopt of ongeldig wordt gemaakt, kunnen honderden gelijktijdige verzoeken tegelijkertijd de server raken. Ze ervaren allemaal een cache-miss en belasten de database voor exact dezelfde query, wat je primaire database plat kan leggen.
Oplossing: Next.js unstable_cache biedt standaard al request-deduplicatie per instantie. Voor gedistribueerde deduplicatie heb je een op Redis gebaseerd vergrendelingsmechanisme (locking) nodig, of kun je vertrouwen op stale-while-revalidate (SWR) patronen, zodat de gebruiker verouderde gegevens te zien krijgt terwijl de revalidatie op de achtergrond plaatsvindt.
4. Verbindingslekken in Serverless
Serverless omgevingen (zoals Vercel functions) bevriezen en herstarten execution contexts. Als je bij elk verzoek een nieuwe Redis-verbinding opent, bereik je direct de verbindingslimiet van Redis.
Oplossing: Instantieer de Redis-client buiten de scope van de request handler. Gebruik op HTTP gebaseerde Redis-clients (zoals de Upstash REST API) als je geen persistente TCP-verbindingen kunt onderhouden, hoewel @neshca/cache-handler persistente clients goed ondersteunt mits correct geconfigureerd.
Het Resultaat
Door een aangepaste Redis cache-handler te implementeren, vervang je de onvoorspelbare, vluchtige bestandssysteemcache door een robuuste, gecentraliseerde datastore.
Je deployments wissen de cache niet langer. Je serverless instanties delen één enkele bron van waarheid. De belasting van je database daalt drastisch en de responstijden binnen het cluster stabiliseren.
Next.js 15 wil caching beheren. Laat het de API beheren. Maar jij moet de infrastructuur beheren. Redis biedt je de controle die vereist is om Next.js op schaal te draaien. Neem geen genoegen met de standaarden.
Seven Labs Dienst
SaaS Ontwikkeling - Next.js & MERN
