diff --git a/CHANGELOG.md b/CHANGELOG.md
index a94744803..be52c9af0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to
## Added
+- 🚀(frontend) Adds api caching #961
- ✨(back) allow theme customnization using a configuration file #948
- ✨ Add a custom callout block to the editor #892
- 🚩(frontend) version MIT only #911
diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py
index 57269b163..8dcb62530 100755
--- a/src/backend/impress/settings.py
+++ b/src/backend/impress/settings.py
@@ -58,7 +58,14 @@ class Base(Configuration):
* DB_USER
"""
- DEBUG = False
+ DEBUG_RAW = values.Value(
+ default=False,
+ environ_name="DEBUG",
+ environ_prefix=None,
+ )
+ DEBUG_NAMESPACES = DEBUG_RAW.split(",") if isinstance(DEBUG_RAW, str) else []
+ DEBUG = bool(DEBUG_RAW)
+
USE_SWAGGER = False
API_VERSION = "v1.0"
@@ -761,6 +768,9 @@ def post_setup(cls):
"OIDC_ALLOW_DUPLICATE_EMAILS cannot be set to True simultaneously. "
)
+ if cls.DEBUG and "no-cache" in cls.DEBUG_NAMESPACES:
+ cls.THEME_CUSTOMIZATION_CACHE_TIMEOUT = 0
+
class Build(Base):
"""Settings used when the application is built.
@@ -795,6 +805,17 @@ class Development(Base):
CSRF_TRUSTED_ORIGINS = ["http://localhost:8072", "http://localhost:3000"]
DEBUG = True
+ # Enable debug namespaces as needed
+ #
+ # DEBUG_NAMESPACES = [
+ # 'no-cache',
+ # 'features:language'
+ # ]
+ #
+ # They can be enabled in all environments
+ # via DEBUG environment variable
+ # DEBUG="features:language,no-cache,..."
+
SESSION_COOKIE_NAME = "impress_sessionid"
USE_SWAGGER = True
diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json
index d9ecfdece..b7713c200 100644
--- a/src/frontend/apps/impress/package.json
+++ b/src/frontend/apps/impress/package.json
@@ -31,7 +31,9 @@
"@openfun/cunningham-react": "3.0.0",
"@react-pdf/renderer": "4.3.0",
"@sentry/nextjs": "9.15.0",
- "@tanstack/react-query": "5.75.4",
+ "@tanstack/query-sync-storage-persister": "5.76.0",
+ "@tanstack/react-query": "5.75.5",
+ "@tanstack/react-query-persist-client": "5.76.0",
"canvg": "4.0.3",
"clsx": "2.1.1",
"cmdk": "1.1.1",
diff --git a/src/frontend/apps/impress/src/core/AppProvider.tsx b/src/frontend/apps/impress/src/core/AppProvider.tsx
index 03ce5097d..20eb0d94a 100644
--- a/src/frontend/apps/impress/src/core/AppProvider.tsx
+++ b/src/frontend/apps/impress/src/core/AppProvider.tsx
@@ -1,7 +1,11 @@
import { CunninghamProvider } from '@openfun/cunningham-react';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
+import { QueryClient } from '@tanstack/react-query';
+import type { Persister } from '@tanstack/react-query-persist-client';
+import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
+import debug from 'debug';
import { useRouter } from 'next/router';
-import { useEffect } from 'react';
+import { useEffect, useMemo } from 'react';
import { useCunninghamTheme } from '@/cunningham';
import { Auth, KEY_AUTH, setAuthUrl } from '@/features/auth';
@@ -13,12 +17,16 @@ import { ConfigProvider } from './config/';
* QueryClient:
* - defaultOptions:
* - staleTime:
- * - global cache duration - we decided 3 minutes
- * - It can be overridden to each query
+ * - global time until cache is considered stale and will be refetched in the background
+ * - instant if debug flag "no-cache" active - 3 minutes otherwise
+ * - gcTime:
+ * - global time until cache is purged from the persister and needs to be renewed
+ * - since its cached in localStorage, we can set it to a long time (48h)
*/
const defaultOptions = {
queries: {
- staleTime: 1000 * 60 * 3,
+ staleTime: debug.enabled('no-cache') ? 0 : 1000 * 60 * 3, // 3 minutes
+ gcTime: debug.enabled('no-cache') ? 0 : 1000 * 60 * 60 * 48, // 48 hours
retry: 1,
},
};
@@ -29,11 +37,21 @@ const queryClient = new QueryClient({
export function AppProvider({ children }: { children: React.ReactNode }) {
const { theme } = useCunninghamTheme();
const { replace } = useRouter();
-
const initializeResizeListener = useResponsiveStore(
(state) => state.initializeResizeListener,
);
+ const persister = useMemo(() => {
+ // Create persister only when the browser is available
+ if (typeof window !== 'undefined') {
+ return createSyncStoragePersister({
+ storage: window.localStorage,
+ });
+ }
+ // Return undefined otherwise (PersistQueryClientProvider handles undefined persister gracefully)
+ return undefined;
+ }, []);
+
useEffect(() => {
return initializeResizeListener();
}, [initializeResizeListener]);
@@ -60,12 +78,15 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
}, [replace]);
return (
-
+
{children}
-
+
);
}
diff --git a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx
index fdfe8c97e..f1e594b2b 100644
--- a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx
+++ b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx
@@ -1,4 +1,5 @@
import { useQuery } from '@tanstack/react-query';
+import debug from 'debug';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Theme } from '@/cunningham/';
@@ -19,45 +20,24 @@ interface ConfigResponse {
SENTRY_DSN?: string;
}
-const LOCAL_STORAGE_KEY = 'docs_config';
-
-function getCachedTranslation() {
- try {
- const jsonString = localStorage.getItem(LOCAL_STORAGE_KEY);
- return jsonString ? (JSON.parse(jsonString) as ConfigResponse) : undefined;
- } catch {
- return undefined;
- }
-}
-
-function setCachedTranslation(translations: ConfigResponse) {
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(translations));
-}
-
export const getConfig = async (): Promise => {
const response = await fetchAPI(`config/`);
if (!response.ok) {
- throw new APIError('Failed to get the doc', await errorCauses(response));
+ throw new APIError('Failed to get the config', await errorCauses(response));
}
const config = response.json() as Promise;
- setCachedTranslation(await config);
return config;
};
-export const KEY_CONFIG = 'config';
+export const QKEY_CONFIG = 'config';
export function useConfig() {
- const cachedData = getCachedTranslation();
- const oneHour = 1000 * 60 * 60;
-
return useQuery({
- queryKey: [KEY_CONFIG],
+ queryKey: [QKEY_CONFIG],
queryFn: () => getConfig(),
- initialData: cachedData,
- staleTime: oneHour,
- initialDataUpdatedAt: Date.now() - oneHour, // Force initial data to be considered stale
+ staleTime: debug.enabled('no-cache') ? 0 : 1000 * 60 * 60, // 1 hour
});
}
diff --git a/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx b/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx
index 026beec9f..eefb436b3 100644
--- a/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx
+++ b/src/frontend/apps/impress/src/features/auth/api/useAuthQuery.tsx
@@ -1,4 +1,5 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
+import debug from 'debug';
import { APIError, errorCauses, fetchAPI } from '@/api';
@@ -33,7 +34,7 @@ export function useAuthQuery(
return useQuery({
queryKey: [KEY_AUTH],
queryFn: getMe,
- staleTime: 1000 * 60 * 15, // 15 minutes
+ staleTime: debug.enabled('no-cache') ? 0 : 1000 * 60 * 15, // 15 minutes
...queryConfig,
});
}
diff --git a/src/frontend/apps/impress/src/pages/_app.tsx b/src/frontend/apps/impress/src/pages/_app.tsx
index 683341c28..37b98bf97 100644
--- a/src/frontend/apps/impress/src/pages/_app.tsx
+++ b/src/frontend/apps/impress/src/pages/_app.tsx
@@ -18,6 +18,28 @@ export default function App({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page);
const { t } = useTranslation();
+ if (process.env.NODE_ENV === 'development') {
+ /**
+ * Enable debug namespaces as needed
+ *
+ * They can be enabled:
+ *
+ * via DEBUG environment variable
+ * DEBUG="features:language,no-cache,..."
+ *
+ * via browser console
+ * window.debug = "features:language,no-cache,...";
+ *
+ * via Local storage
+ * window.localStorage.debug = "features:language,no-cache,...";
+ *
+ * via code (uses Local storage)
+ * import debug from 'debug';
+ * debug.enable('no-cache,features:language,...');
+ */
+ //debug.enable('no-cache,features:language');
+ }
+
return (
<>
diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock
index 2cb36297b..1b98bf3b0 100644
--- a/src/frontend/yarn.lock
+++ b/src/frontend/yarn.lock
@@ -4881,16 +4881,36 @@
dependencies:
"@typescript-eslint/utils" "^8.18.1"
-"@tanstack/query-core@5.75.4":
- version "5.75.4"
- resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.75.4.tgz#e05f2fe4145fb5354271ad19e63eec61f6ce3012"
- integrity sha512-pcqOUgWG9oGlzkfRQQMMsEFmtQu0wq81A414CtELZGq+ztVwSTAaoB3AZRAXQJs88LmNMk2YpUKuQbrvzNDyRg==
+"@tanstack/query-core@5.75.5":
+ version "5.75.5"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.75.5.tgz#198c27c21b5d15aad09167af8a021f214e194d4b"
+ integrity sha512-kPDOxtoMn2Ycycb76Givx2fi+2pzo98F9ifHL/NFiahEDpDwSVW6o12PRuQ0lQnBOunhRG5etatAhQij91M3MQ==
+
+"@tanstack/query-core@5.76.0":
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.76.0.tgz#3b4d5d34ce307ba0cf7d1a3e90d7adcdc6c46be0"
+ integrity sha512-FN375hb8ctzfNAlex5gHI6+WDXTNpe0nbxp/d2YJtnP+IBM6OUm7zcaoCW6T63BawGOYZBbKC0iPvr41TteNVg==
"@tanstack/query-devtools@5.74.7":
version "5.74.7"
resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.74.7.tgz#c9b022b386ac86e6395228b5d6912e6444b3b971"
integrity sha512-nSNlfuGdnHf4yB0S+BoNYOE1o3oAH093weAYZolIHfS2stulyA/gWfSk/9H4ZFk5mAAHb5vNqAeJOmbdcGPEQw==
+"@tanstack/query-persist-client-core@5.76.0":
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.76.0.tgz#a3bcdd687384dc6b5b61b402bef153ad54515321"
+ integrity sha512-xcTZjILf4q49Nsl6wcnhBYZ4O0gpnuNwV6vPIEWIrwTuSNWz2zd/g9bc8SxnXy7xCV8SM1H0IJn8KjLQIUb2ag==
+ dependencies:
+ "@tanstack/query-core" "5.76.0"
+
+"@tanstack/query-sync-storage-persister@5.76.0":
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.76.0.tgz#29643062f1a424873afb22032ce70ee72436bb9b"
+ integrity sha512-N8d8voY61XkM+jfXTySduLrevD6wRM3pwQ1kG0syLiWWx/sX2+CpaTMSPr0GggjQuhmjhUPo83LaV+e449tizA==
+ dependencies:
+ "@tanstack/query-core" "5.76.0"
+ "@tanstack/query-persist-client-core" "5.76.0"
+
"@tanstack/react-query-devtools@5.75.4":
version "5.75.4"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.75.4.tgz#89614363d63c997ade81ade4a18e90b57512d4d8"
@@ -4898,12 +4918,19 @@
dependencies:
"@tanstack/query-devtools" "5.74.7"
-"@tanstack/react-query@5.75.4":
- version "5.75.4"
- resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.75.4.tgz#721e1cdf7debb110671f558dc2b6276f637554a5"
- integrity sha512-Vf65pzYRkf8fk9SP1ncIZjvaXszBhtsvpf+h45Y/9kOywOrVZfBGUpCdffdsVzbmBzmz6TCFes9bM0d3pRrIsA==
+"@tanstack/react-query-persist-client@5.76.0":
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.76.0.tgz#97718fec844708cb98a5902d4b1eeb72adea555b"
+ integrity sha512-QPKgkHX1yC1Ec21FTQHBTbQcHYI+6157DgsmxABp94H7/ZUJ3szZ7wdpdBPQyZ9VxBXlKRN+aNZkOPC90+r/uA==
+ dependencies:
+ "@tanstack/query-persist-client-core" "5.76.0"
+
+"@tanstack/react-query@5.75.5":
+ version "5.75.5"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.75.5.tgz#650a6b58c7bcd4c33062426e05c4467571148a4f"
+ integrity sha512-QrLCJe40BgBVlWdAdf2ZEVJ0cISOuEy/HKupId1aTKU6gPJZVhSvZpH+Si7csRflCJphzlQ77Yx6gUxGW9o0XQ==
dependencies:
- "@tanstack/query-core" "5.75.4"
+ "@tanstack/query-core" "5.75.5"
"@tanstack/react-table@8.20.6":
version "8.20.6"