Advanced Configuration

Customize the cache behavior using the config.ts file and JopiRouteConfig hooks.

Overview

While the JopiJS cache works out-of-the-box with sensible defaults (enabled for everyone, "autoCache.disable" to turn it off completely), real-world applications often require more granular control.

The config.ts file in your route folder allows you to programmatically configure the cache behavior using the JopiRouteConfig object.

Accessing the Configuration

To configure the cache for a specific route, create (or edit) the config.ts file in that route's folder:

src/mod_example/@routes/my-page/config.ts
import { JopiRouteConfig } from "jopijs";

export default function(config: JopiRouteConfig) {
    // Configure your cache hooks here
    
}

Disabling the Cache

Method 1: The File (Static)

The simplest way to disable caching for a route is to create an empty file named autoCache.disable inside the route folder (next to your page.tsx).

File Structure
src/
└── mod_example/
    └── @routes/
        └── my-page/
            ├── page.tsx
            └── autoCache.disable  <-- Just create this file

Method 2: The Code (Dynamic)

You can also programmatically disable the HTML Cache for the page via config.ts. This is useful if you want to disable it based on certain conditions (though for totally dynamic logic, prefer htmlCache_beforeCheckingCache hooks).

config.onPage.htmlCache_disableAutomaticCache();

Invalidating the Cache

You can manually invalidate cache entries using the req.htmlCache_removeFromCache() function available in the JopiRequest object. This is typically done when content changes (e.g., after an admin update).

1. In Hooks (config.ts)

You can use hooks to conditionally flush the cache for the current page.

// Inside config.ts
config.onPage.htmlCache_beforeCheckingCache(async (req) => {
    // Example: If a specific query param is present, force a refresh
    if (req.req_urlSearchParams.forceRefresh === "true") {
        
        // Remove the current page from cache
        await req.htmlCache_removeFromCache();
        
        // The rest of the request will now generate a fresh page and cache it
    }
});

2. In API Routes

A common pattern is to clear the cache of a public page when an API endpoint modifies the underlying data.

// Inside an API route (e.g., POST /api/update-product)
config.onPOST.add_middleware(async (req) => {
    // 1. Perform your update logic...
    await updateProductInDb(req);

    // 2. Build the URL of the page you want to clear
    const publicPageUrl = new URL("https://site.com/products/my-product");
    
    // 3. Remove that specific page from the cache
    await req.htmlCache_removeFromCache(publicPageUrl);
});

Cache Hooks

JopiJS exposes several hooks in the cache lifecycle allowing you to intercept, modify, or bypass the cache logic.

1. beforeCheckingCache

The htmlCache_beforeCheckingCache hook is the most powerful. It runs before the cache engine even looks for a cached file.

This is the perfect place to bypass the cache for specific users, such as administrators who always need to see the live version of the site to moderate content.

config.onPage.htmlCache_beforeCheckingCache(async (req) => {
    // Determine if the user has the 'admin' role
    if (req.role_userHasRole("admin")) {
        // Force the request to bypass the cache and hit the server logic directly
        req.htmlCache_removeFromCache();
    }
});

2. readCacheEntry

The htmlCache_readCacheEntry hook allows you to replace the default cache reading behavior.

Instead of JopiJS looking up the file in its internal storage (file system or memory), your function will be called. If you return a Response, JopiJS treats it as a cache hit. If you return undefined, it treats it as a cache miss and proceeds to generate the page.

config.onPage.htmlCache_readCacheEntry(async (req) => {
    // Custom logic to retrieve the page from an external source (e.g., Redis, CDN)
    const customCache = await myCustomCache.get(req.url);
    
    if (customCache) {
        return new Response(customCache.body, { headers: customCache.headers });
    }
    
    return undefined; // Cache miss
});

3. beforeAddToCache

The htmlCache_beforeAddToCache hook allows you to inspect or modify the generated response before it is saved to the cache storage.

Important Header Rules: By default, the cache engine only preserves the following headers:

  • Content-Type
  • ETag (automatically recalculated)
  • Last-Modified

Any other custom header you add to the response will be discarded.

4. afterGetFromCache

The htmlCache_afterGetFromCache hook runs after a response has been retrieved from the cache but before it is sent to the client.

Use this for:

  • Adding dynamic headers that shouldn't be frozen in time (e.g., current server time, request ID).
  • Logging cache hits.
config.onPage.htmlCache_afterGetFromCache(async (req, res) => {
    // Log a cache hit
    console.log(`Cache HIT for ${req.url}`);
    
    // Add a dynamic header
    res.headers.set("X-Cache-Status", "HIT");
    
    return res;
});

5. ifNotInCache

The htmlCache_ifNotInCache hook is triggered when a request is made but the page is not found in the cache (a "cache miss").

This is primarily useful for:

  • Logging cache misses.
  • Triggering background jobs or pre-fetching related data.
config.onPage.htmlCache_ifNotInCache((req) => {
    console.log(`Cache MISS for ${req.url} - Generating page...`);
});

Global Cache Rules

For broader control over caching policies, you can define Global Cache Rules at the application level. This is perfect for applying rules to entire sections of your website (e.g., all /api routes) without having to configure each route individually.

Use the add_cacheRules method within the configure_htmlCache() block of your server initialization (typically src/index.ts).

Usage Example

src/mod_api/serverInit.ts
import { JopiWebSiteBuilder } from "jopijs";

export default async function(app: JopiWebSiteBuilder) {
    app.configure_htmlCache()
        .add_cacheRules({
            // 1. Define the scope of the rule using RouteSelector
            routeSelector: {
                fromPath: "/api",
                exclude: ["/api/health", "/api/metrics"], // Don't apply this rule to these paths
            },

            // 2. Define the behavior
            // Example: Disable automatic caching for all API routes except health/metrics
            disableAutomaticCache: true,
            
            // You can also use the standard hooks here:
            // beforeCheckingCache: ...,
            // afterGetFromCache: ...,
            // beforeAddToCache: ...,
            // ifNotInCache: ...,
        });
}