Skip to content

Commit e5d9ea5

Browse files
authored
[codex] mac詳細ページのオフライン再読み込み対応 (#2)
* 不要な公開SVGアセットを削除 * mac詳細ページをService Workerでオフライン表示対応
1 parent 38cd16e commit e5d9ea5

File tree

3 files changed

+131
-6
lines changed

3 files changed

+131
-6
lines changed

app/layout.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AuthProvider } from "@/contexts/auth-context";
22
import { AuthGuard } from "@/components/auth/auth-guard";
3+
import { ServiceWorkerRegistration } from "@/components/pwa/ServiceWorkerRegistration";
34
import { TooltipProvider } from "@/components/ui/tooltip";
45
import type { Metadata } from "next";
56
import { Noto_Sans_JP } from "next/font/google";
@@ -26,12 +27,13 @@ export default function RootLayout({
2627
className={`${notoSansJP.variable} h-full antialiased`}
2728
>
2829
<body className="min-h-full flex flex-col">
29-
<TooltipProvider>
30-
<AuthProvider>
31-
<AuthGuard>{children}</AuthGuard>
32-
</AuthProvider>
33-
</TooltipProvider>
34-
</body>
30+
<TooltipProvider>
31+
<AuthProvider>
32+
<AuthGuard>{children}</AuthGuard>
33+
</AuthProvider>
34+
</TooltipProvider>
35+
<ServiceWorkerRegistration />
36+
</body>
3537
</html>
3638
);
3739
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
3+
import { useEffect } from "react";
4+
5+
export function ServiceWorkerRegistration() {
6+
useEffect(() => {
7+
if (process.env.NODE_ENV !== "production") return;
8+
if (!("serviceWorker" in navigator)) return;
9+
10+
const register = async () => {
11+
try {
12+
await navigator.serviceWorker.register("/sw.js", { scope: "/" });
13+
} catch {
14+
// Service worker registration is best-effort.
15+
}
16+
};
17+
18+
void register();
19+
}, []);
20+
21+
return null;
22+
}

public/sw.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
const SW_VERSION = "v1";
2+
const MAC_DOCUMENT_CACHE = `mac-documents-${SW_VERSION}`;
3+
const STATIC_ASSET_CACHE = `static-assets-${SW_VERSION}`;
4+
5+
const CURRENT_CACHES = [MAC_DOCUMENT_CACHE, STATIC_ASSET_CACHE];
6+
7+
self.addEventListener("install", (event) => {
8+
event.waitUntil(self.skipWaiting());
9+
});
10+
11+
self.addEventListener("activate", (event) => {
12+
event.waitUntil(
13+
(async () => {
14+
const cacheNames = await caches.keys();
15+
await Promise.all(
16+
cacheNames
17+
.filter((cacheName) => !CURRENT_CACHES.includes(cacheName))
18+
.map((cacheName) => caches.delete(cacheName))
19+
);
20+
await self.clients.claim();
21+
})()
22+
);
23+
});
24+
25+
self.addEventListener("fetch", (event) => {
26+
const { request } = event;
27+
if (request.method !== "GET") return;
28+
29+
if (isMacNavigationRequest(request)) {
30+
event.respondWith(cacheFirstNavigation(request));
31+
return;
32+
}
33+
34+
if (isCacheableStaticAssetRequest(request)) {
35+
event.respondWith(staleWhileRevalidate(request));
36+
}
37+
});
38+
39+
function isMacNavigationRequest(request) {
40+
if (request.mode !== "navigate") return false;
41+
42+
const url = new URL(request.url);
43+
if (url.origin !== self.location.origin) return false;
44+
45+
return url.pathname === "/mac" || url.pathname.startsWith("/mac/");
46+
}
47+
48+
function isCacheableStaticAssetRequest(request) {
49+
const url = new URL(request.url);
50+
if (url.origin !== self.location.origin) return false;
51+
52+
if (url.pathname.startsWith("/_next/static/")) return true;
53+
54+
return ["script", "style", "font", "image"].includes(request.destination);
55+
}
56+
57+
async function cacheFirstNavigation(request) {
58+
const cache = await caches.open(MAC_DOCUMENT_CACHE);
59+
const cached = await cache.match(request);
60+
if (cached) {
61+
return cached;
62+
}
63+
64+
try {
65+
const response = await fetch(request);
66+
if (response.ok) {
67+
await cache.put(request, response.clone());
68+
}
69+
return response;
70+
} catch {
71+
return new Response("オフラインです。接続後に再試行してください。", {
72+
status: 503,
73+
headers: { "Content-Type": "text/plain; charset=UTF-8" },
74+
});
75+
}
76+
}
77+
78+
async function staleWhileRevalidate(request) {
79+
const cache = await caches.open(STATIC_ASSET_CACHE);
80+
const cached = await cache.match(request);
81+
82+
const networkResponsePromise = fetch(request)
83+
.then((response) => {
84+
if (response.ok) {
85+
void cache.put(request, response.clone());
86+
}
87+
return response;
88+
})
89+
.catch(() => null);
90+
91+
if (cached) {
92+
return cached;
93+
}
94+
95+
const networkResponse = await networkResponsePromise;
96+
if (networkResponse) {
97+
return networkResponse;
98+
}
99+
100+
return new Response("", { status: 504 });
101+
}

0 commit comments

Comments
 (0)