Persisting In-Memory Values Between Renders (Dynamic Rendering)
Learn why in-memory state doesn't survive HTML rendering in dinou when rendering dynamically, and how to persist values like counters using external storage.
Overview
dinou renders HTML in a child process, isolated from the main server process. This means that any global state or in-memory variables are not shared across HTML renders. While the RSC server can retain memory, the HTML renderer cannot.
This can cause hydration mismatches if your page depends on changing in-memory values like counters.
Here's a problematic example:
// src/page_functions.ts
let counter = 0;
export function getProps() {
return { page: { visits: counter++ } }; // ❌ This causes hydration mismatch
}
In the code above, the HTML output will always be 0
, while the hydrated React app will see an incremented value.
File-based Persistence
One way to persist data is through local file storage using Node.js fs
APIs. Here’s an example:
import fs from 'fs';
const path = '.dinou_cache/counter.txt';
export function getCounter(): number {
let value = 0;
if (fs.existsSync(path)) value = parseInt(fs.readFileSync(path, 'utf8'), 10);
fs.writeFileSync(path, String(value + 1));
return value;
}
Redis-based Persistence
You can also use Redis, a fast in-memory data store. This is especially useful in distributed or serverless environments.
import { createClient } from 'redis';
const redis = createClient();
await redis.connect();
export async function getCounter() {
const key = 'dinou:counter';
const val = await redis.get(key);
const num = val ? parseInt(val) : 0;
await redis.set(key, num + 1);
return num;
}
You can use Upstash for a serverless Redis-compatible database.
Best Practices
- Avoid using in-memory state in
getProps()
orrevalidate()
unless it's fully deterministic. - If you need state persistence, rely on external systems like the file system, Redis, or a database.
- Document any assumptions your code makes about state between renders.