From 3608b8e09bee058d44f9672518f5bf5ae6a90460 Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Sat, 21 Feb 2026 05:04:15 +0900 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=89=8D=E5=AE=9F=E8=A3=85=E3=81=AEin?= =?UTF-8?q?memorycache=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=E3=80=81nextjs?= =?UTF-8?q?=E3=81=AEcache=E3=82=92=E4=BD=BF=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[docs_id]/page.tsx | 5 ++- app/lib/cache.ts | 20 --------- app/lib/chatHistory.ts | 92 +++++++++++++++++++++++------------------- next.config.ts | 3 ++ 4 files changed, 56 insertions(+), 64 deletions(-) delete mode 100644 app/lib/cache.ts diff --git a/app/[docs_id]/page.tsx b/app/[docs_id]/page.tsx index 65cc6d7..87470a0 100644 --- a/app/[docs_id]/page.tsx +++ b/app/[docs_id]/page.tsx @@ -6,7 +6,7 @@ import { join } from "node:path"; import { splitMarkdown } from "./splitMarkdown"; import { PageContent } from "./pageContent"; import { ChatHistoryProvider } from "./chatHistory"; -import { getChatFromCache } from "@/lib/chatHistory"; +import { getChatFromCache, initContext } from "@/lib/chatHistory"; import { getLanguageName, pagesList } from "@/pagesList"; import { isCloudflare } from "@/lib/detectCloudflare"; @@ -72,7 +72,8 @@ export default async function Page({ const mdContent = getMarkdownContent(docs_id); const splitMdContent = mdContent.then((text) => splitMarkdown(text)); - const initialChatHistories = getChatFromCache(docs_id); + const context = await initContext(); + const initialChatHistories = getChatFromCache(docs_id, context); return ( = new Map(); -/** - * nodejsにcache apiがないので、web標準のcache APIに相当するものの自前実装 - */ -export const inMemoryCache = { - async put(key: string, response: Response): Promise { - const arrayBuffer = await response.arrayBuffer(); - cacheData.set(key, arrayBuffer); - }, - async match(key: string): Promise { - const arrayBuffer = cacheData.get(key); - if (arrayBuffer) { - return new Response(arrayBuffer); - } - return undefined; - }, - async delete(key: string): Promise { - return cacheData.delete(key); - }, -} as const; diff --git a/app/lib/chatHistory.ts b/app/lib/chatHistory.ts index 1025ed6..d4f9938 100644 --- a/app/lib/chatHistory.ts +++ b/app/lib/chatHistory.ts @@ -6,7 +6,9 @@ import { getDrizzle } from "./drizzle"; import { chat, message } from "@/schema/chat"; import { and, asc, eq } from "drizzle-orm"; import { Auth } from "better-auth"; -import { inMemoryCache } from "./cache"; +import { revalidateTag, unstable_cacheLife } from "next/cache"; +import { isCloudflare } from "./detectCloudflare"; +import { unstable_cacheTag } from "next/cache"; export interface CreateChatMessage { role: "user" | "ai" | "error"; @@ -26,7 +28,7 @@ interface Context { * authが初期化されてなければ初期化し、 * userIdがなければセッションから取得してセットする。 */ -async function initAll(ctx?: Partial): Promise { +export async function initContext(ctx?: Partial): Promise { if (!ctx) { ctx = {}; } @@ -46,15 +48,6 @@ async function initAll(ctx?: Partial): Promise { } return ctx as Context; } -async function getCache() { - if ("caches" in globalThis) { - // worker - return await caches.open("chatHistory"); - } else { - // nodejs - return inMemoryCache; - } -} export async function addChat( docsId: string, @@ -62,7 +55,7 @@ export async function addChat( messages: CreateChatMessage[], context?: Partial ) { - const { drizzle, userId } = await initAll(context); + const { drizzle, userId } = await initContext(context); if (!userId) { throw new Error("Not authenticated"); } @@ -86,13 +79,16 @@ export async function addChat( ) .returning(); - console.log( - `deleting cache for chatHistory/getChat for user ${userId} and docs ${docsId}` - ); - const cache = await getCache(); - await cache.delete( - `${CACHE_KEY_BASE}/getChat?docsId=${docsId}&userId=${userId}` - ); + revalidateTag(`${CACHE_KEY_BASE}/getChat?docsId=${docsId}&userId=${userId}`); + if (isCloudflare()) { + const cache = await caches.open("chatHistory"); + console.log( + `deleting cache for chatHistory/getChat for user ${userId} and docs ${docsId}` + ); + await cache.delete( + `${CACHE_KEY_BASE}/getChat?docsId=${docsId}&userId=${userId}` + ); + } return { ...newChat, @@ -106,7 +102,7 @@ export async function getChat( docsId: string, context?: Partial ): Promise { - const { drizzle, userId } = await initAll(context); + const { drizzle, userId } = await initContext(context); if (!userId) { return []; } @@ -121,35 +117,47 @@ export async function getChat( orderBy: [asc(chat.createdAt)], }); - const cache = await getCache(); - await cache.put( - `${CACHE_KEY_BASE}/getChat?docsId=${docsId}&userId=${userId}`, - new Response(JSON.stringify(chats), { - headers: { "Cache-Control": "max-age=86400, s-maxage=86400" }, - }) - ); + if (isCloudflare()) { + const cache = await caches.open("chatHistory"); + await cache.put( + `${CACHE_KEY_BASE}/getChat?docsId=${docsId}&userId=${userId}`, + new Response(JSON.stringify(chats), { + headers: { "Cache-Control": "max-age=86400, s-maxage=86400" }, + }) + ); + } return chats; } -export async function getChatFromCache( - docsId: string, - context?: Partial -) { - const { drizzle, auth, userId } = await initAll(context); +export async function getChatFromCache(docsId: string, context: Context) { + "use cache"; + unstable_cacheLife("days"); + + // cacheされる関数の中でheader()にはアクセスできない。 + // なので外でinitContext()を呼んだものを引数に渡す必要がある。 + // しかし、drizzleオブジェクトは外から渡せないのでgetChatの中で改めてinitContext()を呼んでdrizzleだけ再初期化している + const { auth, userId } = context; + unstable_cacheTag( + `${CACHE_KEY_BASE}/getChat?docsId=${docsId}&userId=${userId}` + ); + if (!userId) { return []; } - const cache = await getCache(); - const cachedResponse = await cache.match( - `${CACHE_KEY_BASE}/getChat?docsId=${docsId}&userId=${userId}` - ); - if (cachedResponse) { - console.log("Cache hit for chatHistory/getChat"); - const data = (await cachedResponse.json()) as ChatWithMessages[]; - return data; + if (isCloudflare()) { + const cache = await caches.open("chatHistory"); + const cachedResponse = await cache.match( + `${CACHE_KEY_BASE}/getChat?docsId=${docsId}&userId=${userId}` + ); + if (cachedResponse) { + console.log("Cache hit for chatHistory/getChat"); + const data = (await cachedResponse.json()) as ChatWithMessages[]; + return data; + } else { + console.log("Cache miss for chatHistory/getChat"); + } } - console.log("Cache miss for chatHistory/getChat"); - return await getChat(docsId, { drizzle, auth, userId }); + return await getChat(docsId, { auth, userId }); } export async function migrateChatUser(oldUserId: string, newUserId: string) { diff --git a/next.config.ts b/next.config.ts index f05e953..f17b4de 100644 --- a/next.config.ts +++ b/next.config.ts @@ -6,6 +6,9 @@ initOpenNextCloudflareForDev(); const nextConfig: NextConfig = { /* config options here */ output: "standalone", + experimental: { + useCache: true, + }, eslint: { ignoreDuringBuilds: true, },