Page Caching

EcoPages includes a built-in caching system that supports Static, Dynamic, and Incremental Static Regeneration (ISR) strategies. This allows you to serve fresh content with the performance of static pages.

Caching Strategies

You can define the caching behavior for any page using the cache property in the eco.page configuration.

Static (Default)

By default, pages are cached indefinitely. This is perfect for content that rarely changes.

export default eco.page({
	cache: 'static',
	render: () => <h1>Hello World</h1>,
});

These pages are generated once and served from the cache until the server restarts or the cache is manually invalidated.

Dynamic

Dynamic pages are never cached. They are rendered fresh for every request. Use this for personalized content or real-time data.

export default eco.page({
	cache: 'dynamic',
	render: () => <h1>Current Time: {new Date().toISOString()}</h1>,
});

Incremental Static Regeneration (ISR)

ISR allows you to cache pages for a specific duration ("revalidate" time).

  1. First Request: The page is rendered and cached.
  2. Subsequent Requests: The cached page is served immediately.
  3. After Revalidation Time: The next request still gets the cached (stale) version, but it triggers a background regeneration. Future requests will see the updated content.
export default eco.page({
	cache: {
		revalidate: 60, // Seconds
		tags: ['blog', 'posts'],
	},
	render: () => <h1>Latest Updates</h1>,
});

This strategy uses the Stale-While-Revalidate pattern, ensuring visitors always get instant responses.

Global Configuration

You can configure global cache settings in your eco.config.ts file using defineConfig.

// eco.config.ts
import { defineConfig } from '@ecopages/core/config';
import { ecopagesJsxPlugin } from '@ecopages/ecopages-jsx';
 
export default defineConfig({
	rootDir: import.meta.dirname,
	cache: {
		enabled: true,
		defaultStrategy: 'static',
		maxEntries: 1000,
	},
	integrations: [ecopagesJsxPlugin()],
});

Configuration Options

OptionTypeDefaultDescription
enabledbooleantrue in production; false in watch modeControls whether the cache service is active. Set to true to opt into page caching in development watch mode, or false to disable it in any environment.
defaultStrategyCacheStrategy'static'The default caching strategy for pages that do not specify one.
maxEntriesnumber1000Maximum number of items to keep in the default memory store. Oldest items are removed when this limit is reached.
storeCacheStore'memory'The storage backend to use. You can pass a custom class implementing the CacheStore interface (e.g., for Redis).

Custom CacheStore implementations must return keys evicted by set() and keys removed by invalidateByTags() or invalidateByPaths(). Return an empty array when no entries were removed. The page cache uses these keys to keep its dependency tracking in sync with the backing store.

The page cache is disabled by default in ecopages dev and ecopages dev:watch, so each request renders fresh HTML unless cache.enabled is set to true. Production builds enable it by default.

On-Demand Invalidation

You can manually invalidate cached pages locally or via an API handler using tags or specific paths. This is useful for "purging" content when data updates (e.g., from a CMS webhook).

API Handler Example

services.cache is null when caching is disabled, so guard it before use.

// src/pages/api/revalidate.api.ts
import { definePost } from '@ecopages/core';
 
export const POST = definePost({
	handler: async ({ request, services, response }) => {
		if (!services.cache) return response.json({ revalidated: false });
 
		const { tags, paths } = await request.json();
		if (tags) await services.cache.invalidateByTags(tags);
		if (paths) await services.cache.invalidateByPaths(paths);
 
		return response.json({ revalidated: true });
	},
});

Path invalidation matches the pathname across query strings and fragments. Calling invalidateByPaths(['/blog/1']) clears every cached query and development runtime variant for /blog/1.

Headers

EcoPages automatically sets appropriate headers based on the chosen strategy.

StrategyCache-ControlX-Cache
Staticpublic, max-age=0, must-revalidateHIT / MISS
Dynamicno-store, must-revalidateMISS
ISRpublic, max-age=60, stale-while-revalidate=120HIT / MISS / STALE

In development, HTML is always sent with Cache-Control: no-store, must-revalidate, whatever the strategy, so the browser never shows a page from before an edit.

Static file responses use ETags for revalidation. In watch mode, stable non-HTML files use Cache-Control: no-cache. In production, only output paths recorded as content-addressed by the build receive Cache-Control: public, max-age=31536000, immutable; every other file, such as public files, and HTML files regardless of their filename are revalidated with Cache-Control: public, max-age=0, must-revalidate. A pre-compressed .gz variant is sent only to clients that accept gzip, with Vary: Accept-Encoding.