Pug Template Compilation Caching in Express Serverless Functions: Cold-Start vs Warm-Invocation Trade-offs
0 reputation · 24 May 2025, 23:29 UTC
0 reputation · 24 May 2025, 23:29 UTC
Pug's cache option and Express's view engine integration interact at the point where a template is first rendered during a serverless function invocation. The compiled JavaScript function is stored in an in-memory cache keyed by template path, which persists only for the lifetime of the function instance.
The objective is to minimize per-invocation CPU time—and therefore cost—for a low-traffic workload that experiences frequent cold starts. Enabling cache: true avoids recompilation on subsequent renders within the same warm instance, but the first invocation after a cold start still pays the full compilation cost. Pre-compiling templates at startup via pug.compile() can shift that cost to a controlled warm-up phase, yet the Express view engine does not expose a direct hook to inject pre-compiled functions into its internal cache.
app.locals or passed through res.locals, or does it always invoke its own compilation path?cache: true is set, is the compiled function stored per-process or per-request, and how does that behave under concurrent invocations in a single-instance serverless container?29775 reputation · 25 May 2025, 08:45 UTC
1. Re‑using a pre‑compiled function from app.locals or res.locals?
Express’s app.render will always invoke the internal Pug compiler unless the cache:true option is enabled. The compiler looks up a global pug.cache object keyed by the template path. A function you attach to app.locals or res.locals is never consulted by the view engine, so the template will still be re‑compiled on the first render.
2. Scope of the compiled function when cache:true is set?
The cached function lives in the process‑wide pug.cache object. All concurrent invocations that share the same container instance see the same cache entry, so a subsequent request in the same instance is a cache hit. The cache is not per‑request.
3. Supported warm‑up pattern?
Yes. A dedicated “warm‑up” handler that renders every template (or calls pug.compileFile for each file) can populate the cache before real traffic arrives. Because the cache is per‑process, the first real request after the warm‑up will hit the cache and avoid recompilation.
Enable caching in your Express setup:
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
// Express 4.x
app.set('view cache', true);
// Express 5.x – optional, but shows the object form
app.set('view', { cache: true });
Pre‑compile the most frequently used templates at module load (optional but reduces the first‑render cost before the cache is hit):
const pug = require('pug');
const compiledHome = pug.compileFile('views/home.pug');
app.locals.compiledHome = compiledHome;
Note: this step is only a micro‑optimization; the view engine will still call pug.compile unless cache:true is set.
Create a warm‑up route that renders each template once:
app.get('/warmup', (req, res) => {
const templates = ['home', 'about', 'contact'];
templates.forEach(name => {
res.render(name, {}); // renders and stores in cache
});
res.send('Warm‑up complete');
});
Deploy the function and invoke /warmup immediately after deployment or when you know the container will stay warm.
Measure the benefit:
pug.cache after rendering: console.log(Object.keys(pug.cache));Handle template updates:
delete pug.cache[templatePath];process.memoryUsage() if cost is a concern.Could you confirm which Express major version you’re using? The app.set('view cache', true) syntax differs slightly between 4.x and 5.x and may affect the exact cache keying mechanism.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.