Cache Engine & Performance

Scale your application to thousands of users on low-cost hardware using JopiJS's dual-layer caching system.

Overview

Performance is not an optional feature in JopiJS; it is built into the core. JopiJS uses a "Cache-First" approach that combines lightning-fast HTML delivery with intelligent background data refreshing (Stale-While-Revalidate).

This system solves three critical problems:

  • SEO & Google Scores: Delivers a fully rendered page in milliseconds (LCP/TTFB).
  • Database Bottlenecks: Drastically reduces the number of heavy SQL joins by fetching only what's necessary.
  • Server Costs: Allows running complex apps on entry-level VPS by moving compute-heavy tasks to the browser.

The Real Bottleneck: Data Retrieval

Common wisdom says caching is about avoiding the overhead of transforming React into HTML. While true, that's only a small part of the story.

The actual performance killer is the Database.

In a typical dynamic application, a single page might require dozen of database calls, complex joins, and external API requests. These operations are several orders of magnitude slower than simple code execution.

The true purpose of the JopiJS Cache Engine is to store the results of these expensive data operations. By serving a cached page, you aren't just saving CPU cycles on rendering; you are eliminating the wait time for the database to respond, which is often the difference between a 2-second load and a 50ms load.


1. The Anonymous Page Philosophy

To maximize cache efficiency, JopiJS serves anonymous HTML pages.

How it works:

  1. Neutral SSR: When the server generates the HTML, it ignores the user's session cookie, query strings, and URL hashes. Not only does this ensure the resulting HTML is identical for everyone, but it also protects your application from cache poisoning attacks.
  2. Client-side Personalization: Once the browser receives the static HTML, your scripts immediately kick in to personalize the UI (e.g., showing the user's name or cart) using data already stored in the browser or cached inside the HTML himself fetched via API.

2. Layer 1: Static HTML Cache

By default, every route you create is cached as a static HTML file on the server.

  • Status: Enabled by default.
  • Behavior: The first visitor triggers the rendering; subsequent visitors receive the cached file instantly.
  • Bypassing: To disable caching for a specific route, simply create an empty file named autoCache.disable in your route folder.

3. Layer 2: Isomorphic Data Cache (SWR)

While the HTML is static, your data needs to be fresh. JopiJS automates the Stale-While-Revalidate (SWR) pattern through a special file: pageData.ts.

The Lifecycle:

  1. Initial Render: The server calls your logic to get data and embeds it directly into the HTML.
  2. Instant Display: The user sees the page immediately (with potentially "stale" data).
  3. Background Refresh: JopiJS automatically calls the server to fetch current data.
  4. UI Update: The React components re-render automatically once the fresh data arrives.

Step A: Defining the Data (pageData.ts)

Create a pageData.ts file in your route folder. It should export two main functions:

src/mod_catalog/@routes/products/pageData.ts
import type { JopiPageDataProvider } from "jopijs";

const provider: JopiPageDataProvider = {
    /**
     * Returns the data that the page will use to display itself.
     * These data will be stored inside the HTML, into a header
     * and put in cache with this HTML.
     * 
     * Warning: these data are public and easily visible!
    */
    async getDataForCache(params) {
        // Returns the list of products to display with his data.
        //
        // --> Here we will returns about 50 products and do 8 database queries per product.
        //     Total: 400 database queries that are done ONLY ONE TIME.
        //
        // ...
    }

    /**
     * The function updating our data.
     * It is called by the browser to provoke the data update (with roles checking).
    */
    async getRefreshedData(params) {
        // Refresh the data.
        //
        // --> Only returns the price ans stock of the products
        //     because we know that other data have not changed
        //     since adding into the data-cache.
        //
        //     Total: 2 database optimized queries that are done ON EACH PAGE VIEWING.
        //            (without cache it would be 400 queries each time!)
        //
        // ...
    }
};

export default provider;

Step B: Partial Updates & Automatic Merging

One of the most powerful features of the data cache is that getRefreshedData does not need to return the full object.

JopiJS automatically merges the refreshed data into the existing stale data. This allows you to fetch only the fields that are susceptible to frequent changes (like prices or stock levels).

Why this matters: In a typical unoptimized e-commerce setup (like many WooCommerce themes), displaying 50 products might trigger 400+ database queries (8 per product) every time a user refreshes the page.

With JopiJS:

  1. Initial Render: The server renders the page once and caches it.
  2. Background Refresh: getRefreshedData makes just 1 or 2 targeted queries to fetch only the current price/stock for those 50 IDs.
  3. Merge: JopiJS merges these specific values into the cached product list in the browser.

Step C: Consuming the Data (page.tsx)

Use the usePageData hook to access this managed cycle.

src/mod_catalog/@routes/products/page.tsx
import { usePageData } from "jopijs/ui";

export default function ProductList() {
    // Returns stale data first, then updates automatically
    const pageData = usePageData();

    if (pageData.isLoading) return <p>Loading...</p>;

    return (
        <ul>
            {pageData.items.map(item => (
                <li key={item.id}>
                    {item.name} - <strong>${item.price}</strong>
                    {item.stock < 5 && <span> (Limited Stock!)</span>}
                </li>
            ))}
        </ul>
    );
}

4. Why This is Better Than Standard SSR

Standard SSR (like in Next.js or Nuxt) often fragments performance. If your database takes 2 seconds to respond, the user stares at a white screen for 2 seconds.

FeatureStandard SSRJopiJS Cache-First
TTFB (Time to First Byte)Slow (waits for DB)Instant (serves cache)
Server LoadHigh (renders on every hit)Low (renders once)
SEO RankingGoodExcellent (stable & fast)
Connectivity IssuesPage fails to loadPage displays content immediately

5. Advanced Optimization Tip: Targeted SQL

The database is usually the performance bottleneck. JopiJS's merging system allows you to write extremely efficient SQL because you only need to fetch what is truly dynamic.

Focus on what changes

When the browser loads the cached HTML, static information like Title and Description is already present into the HTML cache. Since these rarely change, you don't need to fetch them again during the background refresh.

By focusing only on volatile fields like Price and Stock, you can avoid complex table joins and heavy data transfers.

Example: Refreshing 50 products in just 2 queries Instead of fetching full product objects (which typically triggers 8+ queries per product in unoptimized approach), you can perform simple targeted lookups for all IDs at once:

Query 1: Concurrent Price Update
SELECT id, price FROM products 
WHERE id IN (101, 102, 103, ...);
Query 2: Concurrent Stock Update
SELECT id, stock FROM inventory 
WHERE id IN (101, 102, 103, ...);

The Impact: You move from 400+ database queries (standard unoptimized approach) to just 2 ultra-fast queries.
This allows your site to remain responsive even on a server with very little CPU/RAM.


Summary

  1. HTML Cache: Serves the "skeleton" and initial data in milliseconds.
  2. pageData.ts: Handles the logic for both initial data and background updates.
  3. usePageData(): The hook that ties it all together for your React components.
  4. autoCache.disable: Your emergency brake for routes that must never be cached.