From a91c8ed928a9b9bac265c309a7d6c556ecb82443 Mon Sep 17 00:00:00 2001 From: Linus Flood Date: Mon, 14 Oct 2024 10:08:26 +0200 Subject: [PATCH 1/4] feat: cms cache --- middlewares/bookingFlow.ts | 6 ------ middlewares/cmsContent.ts | 7 ++++-- middlewares/myPages.ts | 10 +++++---- middlewares/utils.ts | 44 ++++++++++++++++++++++++++++++++++++++ middlewares/webView.ts | 4 ++-- 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/middlewares/bookingFlow.ts b/middlewares/bookingFlow.ts index 74c3818f1..098ca9108 100644 --- a/middlewares/bookingFlow.ts +++ b/middlewares/bookingFlow.ts @@ -2,10 +2,6 @@ import { NextResponse } from "next/server" import { bookingFlow } from "@/constants/routes/hotelReservation" -import { resolve as resolveEntry } from "@/utils/entry" -import { findLang } from "@/utils/languages" -import { removeTrailingSlash } from "@/utils/url" - import { getDefaultRequestHeaders } from "./utils" import type { NextMiddleware } from "next/server" @@ -13,8 +9,6 @@ import type { NextMiddleware } from "next/server" import type { MiddlewareMatcher } from "@/types/middleware" export const middleware: NextMiddleware = async (request) => { - const { nextUrl } = request - const headers = getDefaultRequestHeaders(request) return NextResponse.next({ request: { diff --git a/middlewares/cmsContent.ts b/middlewares/cmsContent.ts index 8facd7607..3d931ebbe 100644 --- a/middlewares/cmsContent.ts +++ b/middlewares/cmsContent.ts @@ -6,7 +6,7 @@ import { resolve as resolveEntry } from "@/utils/entry" import { findLang } from "@/utils/languages" import { removeTrailingSlash } from "@/utils/url" -import { getDefaultRequestHeaders } from "./utils" +import { fetchAndCacheEntry, getDefaultRequestHeaders } from "./utils" import type { NextMiddleware } from "next/server" @@ -21,7 +21,10 @@ export const middleware: NextMiddleware = async (request) => { const pathNameWithoutLang = pathWithoutTrailingSlash.replace(`/${lang}`, "") const searchParams = new URLSearchParams(request.nextUrl.searchParams) - const { contentType, uid } = await resolveEntry(pathNameWithoutLang, lang) + const { contentType, uid } = await fetchAndCacheEntry( + pathNameWithoutLang, + lang + ) if (!contentType || !uid) { throw notFound( diff --git a/middlewares/myPages.ts b/middlewares/myPages.ts index fcadc8736..28c375f13 100644 --- a/middlewares/myPages.ts +++ b/middlewares/myPages.ts @@ -9,10 +9,9 @@ import { import { env } from "@/env/server" import { internalServerError, notFound } from "@/server/errors/next" -import { resolve as resolveEntry } from "@/utils/entry" import { findLang } from "@/utils/languages" -import { getDefaultRequestHeaders } from "./utils" +import { fetchAndCacheEntry, getDefaultRequestHeaders } from "./utils" import type { NextMiddleware } from "next/server" @@ -40,8 +39,11 @@ export const middleware: NextMiddleware = async (request) => { } const pathNameWithoutLang = nextUrl.pathname.replace(`/${lang}`, "") - const { uid, contentType } = await resolveEntry(pathNameWithoutLang, lang) - if (!uid) { + const { uid, contentType } = await fetchAndCacheEntry( + pathNameWithoutLang, + lang + ) + if (!uid || !contentType) { throw notFound( `Unable to resolve CMS entry for locale "${lang}": ${pathNameWithoutLang}` ) diff --git a/middlewares/utils.ts b/middlewares/utils.ts index 0cf1c5b46..9c9ce60d9 100644 --- a/middlewares/utils.ts +++ b/middlewares/utils.ts @@ -1,5 +1,9 @@ +import { stringify } from "querystring" + +import { Lang } from "@/constants/languages" import { env } from "@/env/server" +import { resolve as resolveEntry } from "@/utils/entry" import { findLang } from "@/utils/languages" import { removeTrailingSlash } from "@/utils/url" @@ -31,3 +35,43 @@ export function getDefaultRequestHeaders(request: NextRequest) { return headers } + +type ContentStackResponse = { + contentType: string | null + uid: string | null + expiresAt: number +} + +const entryResponseCache: Map = new Map() +let size: number = 0 + +export const fetchAndCacheEntry = async (path: string, lang?: Lang) => { + const cacheKey = `${path + lang}` + const cachedResponse = entryResponseCache.get(cacheKey) + + if (cachedResponse && cachedResponse.expiresAt > Date.now() / 1000) { + console.log("[CMS MIDDLEWARE]: CACHE HIT") + return cachedResponse + } + + if (cachedResponse && cachedResponse.expiresAt < Date.now() / 1000) { + console.log("[CMS MIDDLEWARE]: CACHE STALE") + size -= JSON.stringify(cachedResponse).length + entryResponseCache.delete(cacheKey) + } else { + console.log("[CMS MIDDLEWARE]: CACHE MISS") + } + + const { contentType, uid } = await resolveEntry(path, lang) + const expiresAt = Date.now() / 1000 + 3600 + const entryCache = { contentType, uid, expiresAt } + size += JSON.stringify(entryCache).length + console.log("[CMS MIDDLEWARE] Adding to cache", entryCache) + console.log("[CMS MIDDLEWARE] Cache size (total)", size) + entryResponseCache.set(cacheKey, entryCache) + + return { + contentType, + uid, + } +} diff --git a/middlewares/webView.ts b/middlewares/webView.ts index d29001796..09beb8020 100644 --- a/middlewares/webView.ts +++ b/middlewares/webView.ts @@ -13,7 +13,7 @@ import { decryptData } from "@/utils/aes" import { resolve as resolveEntry } from "@/utils/entry" import { findLang } from "@/utils/languages" -import { getDefaultRequestHeaders } from "./utils" +import { fetchAndCacheEntry, getDefaultRequestHeaders } from "./utils" import type { MiddlewareMatcher } from "@/types/middleware" @@ -64,7 +64,7 @@ export const middleware: NextMiddleware = async (request) => { const pathNameWithoutLang = nextUrl.pathname.replace(`/${lang}/webview`, "") - const { uid } = await resolveEntry(pathNameWithoutLang, lang) + const { uid } = await fetchAndCacheEntry(pathNameWithoutLang, lang) if (!uid) { throw notFound( `Unable to resolve CMS entry for locale "${lang}": ${pathNameWithoutLang}` From 697d571ab21d5b2f8820cbff2659e5d303c418c4 Mon Sep 17 00:00:00 2001 From: Linus Flood Date: Mon, 14 Oct 2024 10:19:29 +0200 Subject: [PATCH 2/4] Fixed expiresAt value depending of cache hit/miss --- middlewares/utils.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/middlewares/utils.ts b/middlewares/utils.ts index 9c9ce60d9..47cb5873d 100644 --- a/middlewares/utils.ts +++ b/middlewares/utils.ts @@ -63,7 +63,12 @@ export const fetchAndCacheEntry = async (path: string, lang?: Lang) => { } const { contentType, uid } = await resolveEntry(path, lang) - const expiresAt = Date.now() / 1000 + 3600 + let expiresAt = Date.now() / 1000 + if (!contentType || !uid) { + expiresAt += 600 + } else { + expiresAt += 3600 * 12 + } const entryCache = { contentType, uid, expiresAt } size += JSON.stringify(entryCache).length console.log("[CMS MIDDLEWARE] Adding to cache", entryCache) From a8f7c62f59465d032c99f517e4042a6fa347cc26 Mon Sep 17 00:00:00 2001 From: Linus Flood Date: Mon, 14 Oct 2024 10:36:13 +0200 Subject: [PATCH 3/4] refactor --- middlewares/utils.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/middlewares/utils.ts b/middlewares/utils.ts index 47cb5873d..0d559c2a4 100644 --- a/middlewares/utils.ts +++ b/middlewares/utils.ts @@ -36,13 +36,14 @@ export function getDefaultRequestHeaders(request: NextRequest) { return headers } -type ContentStackResponse = { - contentType: string | null - uid: string | null - expiresAt: number -} - -const entryResponseCache: Map = new Map() +const entryResponseCache: Map< + string, + { + contentType: string | null + uid: string | null + expiresAt: number + } +> = new Map() let size: number = 0 export const fetchAndCacheEntry = async (path: string, lang?: Lang) => { From 10aa735a2ee5c5a72f8364c267fdd21ac61d0bda Mon Sep 17 00:00:00 2001 From: Linus Flood Date: Mon, 14 Oct 2024 10:39:53 +0200 Subject: [PATCH 4/4] Refactor --- middlewares/utils.ts | 2 +- middlewares/webView.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/middlewares/utils.ts b/middlewares/utils.ts index 0d559c2a4..91e06a68d 100644 --- a/middlewares/utils.ts +++ b/middlewares/utils.ts @@ -46,7 +46,7 @@ const entryResponseCache: Map< > = new Map() let size: number = 0 -export const fetchAndCacheEntry = async (path: string, lang?: Lang) => { +export const fetchAndCacheEntry = async (path: string, lang: Lang) => { const cacheKey = `${path + lang}` const cachedResponse = entryResponseCache.get(cacheKey) diff --git a/middlewares/webView.ts b/middlewares/webView.ts index 09beb8020..e06cefb0b 100644 --- a/middlewares/webView.ts +++ b/middlewares/webView.ts @@ -19,7 +19,7 @@ import type { MiddlewareMatcher } from "@/types/middleware" export const middleware: NextMiddleware = async (request) => { const { nextUrl } = request - const lang = findLang(nextUrl.pathname) + const lang = findLang(nextUrl.pathname)! const loginTypeHeader = request.headers.get("loginType") const loginTypeSearchParam = nextUrl.searchParams.get("loginType")