Skip to content

fix: cache OpenAPI spec in sessionStorage to avoid fetch on every mount - #673

Open
Heazzy500 wants to merge 1 commit into
Savitura:mainfrom
Heazzy500:cache-openapi-spec
Open

fix: cache OpenAPI spec in sessionStorage to avoid fetch on every mount#673
Heazzy500 wants to merge 1 commit into
Savitura:mainfrom
Heazzy500:cache-openapi-spec

Conversation

@Heazzy500

@Heazzy500 Heazzy500 commented Aug 3, 2026

Copy link
Copy Markdown

Overview

This PR fixes a low-severity performance bug in the Developer page where the OpenAPI specification (/api/v1/docs/openapi.json) was fetched from the network on every single mount of src/pages/Developer.jsx. Since the spec is static for the lifetime of a deployment and is unlikely to change within a browser session, this caused:

  • Unnecessary network requests — every navigation to /developer triggered a fetch to the API
  • Slower perceived page load — the API explorer section rendered empty until the spec was fully parsed
  • Unnecessary server load — redundant requests for a static, unchanging resource

The fix caches the parsed endpoint list in sessionStorage with a deployment-scoped cache key. Subsequent mounts within the same session read from cache instantly, eliminating the network roundtrip. The cache is automatically invalidated when the user closes the tab (session storage), which is the correct TTL — the spec only changes on deployment restart.

Related Issue

Closes #580

Changes

[FIX] frontend/src/pages/Developer.jsxloadOpenApi function

Problem

The loadOpenApi function (lines 61-113) unconditionally fetches the OpenAPI spec on every mount:

useEffect(() => {
    refresh();
    loadOpenApi();  // ← fetch on every mount, no caching
}, []);

This means:

  • Navigate to /dashboard → navigate back to /developerre-fetches spec
  • Navigate to /campaigns → navigate back to /developerre-fetches spec
  • Any route change away from and back to Developer → re-fetches spec

Fix

A sessionStorage cache check is inserted at the top of loadOpenApi, before the network fetch:

async function loadOpenApi() {
    try {
      // Cache hit: the spec is unlikely to change during a session (Issue #580).
      const cacheKey = `cp_openapi_spec_${V1_API_BASE}`;
      const cachedRaw = sessionStorage.getItem(cacheKey);
      if (cachedRaw) {
        try {
          const cached = JSON.parse(cachedRaw);
          if (Array.isArray(cached) && cached.length > 0) {
            setV1Endpoints(cached);
            const saved = localStorage.getItem('cp_explorer_endpoint');
            if (saved && cached.find((e) => e.id === saved)) {
              setExplorerEndpoint(saved);
            } else if (cached.length > 0) {
              setExplorerEndpoint(cached[0].id);
            }
            return;  // ← early return, skip network fetch
          }
        } catch {
          // Corrupted cache — silently re-fetch.
          sessionStorage.removeItem(cacheKey);
        }
      }
      // ... existing fetch logic ...

After a successful network fetch and parse, the result is written to cache:

      setV1Endpoints(endpoints);

      // Cache the parsed endpoints in sessionStorage so subsequent mounts
      // skip the network roundtrip (Issue #580).
      try {
        sessionStorage.setItem(cacheKey, JSON.stringify(endpoints));
      } catch {
        // Storage full or unavailable — non-critical, the endpoints are already
        // in component state.
      }

Design decisions

Decision Rationale
sessionStorage over localStorage The spec only changes on deployment restart, which coincides with a new browser session. sessionStorage avoids stale-cache bugs if the deployment updates mid-session (rare but possible with hot-reload dev setups).
sessionStorage over in-memory/state In-memory state (e.g., a module-level variable) would work for SPA navigation but would NOT survive a full page reload. sessionStorage survives reloads while still being session-scoped.
sessionStorage over TTL-based caching The issue suggests a TTL approach, but sessionStorage's lifetime is the natural TTL here — the spec is deployment-static and the session ends on tab close. No timer management needed.
Deployment-scoped cache key The key includes V1_API_BASE (cp_openapi_spec_${V1_API_BASE}), so different deployments (staging vs production) get separate cache entries.
Silent corruption handling If JSON.parse fails on a cached entry (e.g., truncated write), the corrupt entry is removed and the spec is re-fetched. No user-facing error — the page still works. The try/catch on sessionStorage.setItem handles the edge case where storage is full or disabled (private browsing in some browsers).
Cache validation The cached value is validated (Array.isArray(cached) && cached.length > 0) before being used, preventing empty or malformed cache entries from rendering a broken API explorer.
Preserves existing behavior The cache hit path restores explorerEndpoint from localStorage exactly as the original code did after a network fetch, so the user's last-used endpoint is preserved across cached and non-cached code paths.

No changes to other storage usage

The component already uses localStorage for cp_explorer_endpoint (persisting the selected endpoint across sessions) and cp_explorer_params_* (persisting form state with a 400ms debounce). These are intentionally left as localStorage — endpoint selection is user preference that should survive sessions, and parameter persistence is a convenience feature. Only the fetched data (the spec) is moved to sessionStorage.

Files Changed

File Lines Description
frontend/src/pages/Developer.jsx +31 / −0 Add sessionStorage caching in loadOpenApi
Total +31 / −0

Verification Results

Full test suite

$ npm test

 ✓ src/App.test.jsx (1 test)
 ✓ src/components/CampaignCard.test.jsx (6 tests)
 ✓ src/components/Footer.test.jsx (2 tests)
 ... (40 more test files)

 Test Files  44 passed (44)
      Tests  153 passed (153)
   Duration  50.98s

153/153 tests pass — no regressions. All 44 test files pass, including existing component tests that exercise the Developer page indirectly.

Manual verification checklist (for reviewer)

  • Navigate to /developer — API explorer populates normally
  • Navigate away to another page, then back to /developer — API explorer populates instantly (cache hit)
  • Open DevTools → Application → Session Storage — confirm cp_openapi_spec_* key exists with valid JSON
  • Close tab, reopen, navigate to /developer — spec is re-fetched (session storage cleared)
  • Hard refresh (Cmd+Shift+R) on /developer — cache is used (session storage survives reload)

Acceptance Criteria

Criteria Status Evidence
Spec cached in sessionStorage on first load sessionStorage.setItem(cacheKey, JSON.stringify(endpoints)) after successful fetch
Subsequent mounts use cache (no network request) Early return before fetch() when cache hit is valid
Corrupted cache silently falls back to fetch try/catch around JSON.parse; corrupted entry removed before re-fetch
Cache write failure is non-fatal try/catch around sessionStorage.setItem
Deployment-scoped cache key Key includes V1_API_BASE
Cache preserves explorer endpoint selection localStorage.getItem('cp_explorer_endpoint') check on cache hit path
Existing tests pass unchanged 153/153 tests pass
Change is minimal and focused 1 file, +31/−0

Out of Scope (intentional)

  • Service Worker caching. The spec could also be cached at the HTTP level via Cache-Control headers. This is a backend concern and not addressed here.
  • Cache invalidation on spec version change. If the backend API version changes within a session (e.g., during hot-reload development), the cached spec will be stale until the tab is closed. This is acceptable because production deployments are atomic (all routes update together) and development use of sessionStorage is already ephemeral.
  • Preloading the spec. A future optimization could preload the spec in usePreload.js or a route loader, but this would be a broader architectural change outside the scope of this bug fix.

…nt (Savitura#580)

The OpenAPI spec was fetched from the network every time the Developer
page was mounted, causing unnecessary network requests and slower page
load. Cache the parsed endpoints in sessionStorage so subsequent mounts
within the same session skip the network roundtrip.

The cache key includes the API base URL for correct isolation across
deployments. Corrupted cache entries are silently discarded and
re-fetched.
@drips-wave

drips-wave Bot commented Aug 3, 2026

Copy link
Copy Markdown

@Heazzy500 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: Developer.jsx loadOpenApi fetches spec on every mount

1 participant