diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index c2ec8bcd..00000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/images/banner.jpg b/public/images/banner.jpg
new file mode 100644
index 00000000..f208b0ff
Binary files /dev/null and b/public/images/banner.jpg differ
diff --git a/public/images/logo.png b/public/images/logo.png
new file mode 100644
index 00000000..d77176e6
Binary files /dev/null and b/public/images/logo.png differ
diff --git a/public/index.html b/public/index.html
index d530da62..80d656d5 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,10 +2,13 @@
-
-
-
-
+
+
+
+
+
+
+
@@ -29,15 +32,23 @@
-
-
+
+
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 00000000..c7183be8
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,31 @@
+{
+ "short_name": "Piickle",
+ "name": "Piickle",
+ "description": "지금 내게 필요한 대화주제 추천 서비스, Piickle",
+ "theme_color": "#000000",
+ "background_color": "#ffffff",
+ "dir": "ltr",
+ "display": "standalone",
+ "orientation": "any",
+ "scope": "/",
+ "start_url": ".",
+ "icons": [
+ {
+ "src": "images/logo.png",
+ "type": "image/x-icon",
+ "sizes": "512x512",
+ "purpose": "any maskable"
+ },
+ {
+ "src": "images/logo.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ },
+ {
+ "src": "images/banner.jpg",
+ "type": "image/jpg",
+ "sizes": "480x288",
+ "purpose": "any maskable"
+ }
+ ]
+}
diff --git a/public/offline.html b/public/offline.html
new file mode 100644
index 00000000..b7f29fa2
--- /dev/null
+++ b/public/offline.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ No Internet Connection
+
+
+
+
+ 인터넷 연결에 실패하였습니다
+ 인터넷 연결을 확인하고 다시 시도해주십시오
+
+
+
\ No newline at end of file
diff --git a/public/pwaServiceWorker.js b/public/pwaServiceWorker.js
new file mode 100644
index 00000000..7cbfe2f1
--- /dev/null
+++ b/public/pwaServiceWorker.js
@@ -0,0 +1,68 @@
+/* eslint-disable no-undef */
+importScripts("https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js");
+
+const CACHE = "pwabuilder-page";
+
+const offlineFallbackPage = "offline.html";
+const urlsToCache = ["index.html", offlineFallbackPage];
+
+self.addEventListener("message", (event) => {
+ if (event.data && event.data.type === "SKIP_WAITING") {
+ self.skipWaiting();
+ }
+});
+
+// SW 설치
+self.addEventListener("install", async (event) => {
+ event.waitUntil(
+ caches.open(CACHE).then((cache) => {
+ console.log("Opened Cache");
+ return cache.addAll(urlsToCache);
+ }),
+ );
+});
+
+if (workbox.navigationPreload.isSupported()) {
+ workbox.navigationPreload.enable();
+}
+
+// Listen for requests
+self.addEventListener("fetch", (event) => {
+ if (event.request.mode === "navigate") {
+ event.respondWith(
+ (async () => {
+ try {
+ // 만약 preload 된 response가 있으면 받는다
+ const preloadResp = await event.preloadResponse;
+ if (preloadResp) return preloadResp;
+
+ // 만약 없으면 network를 이용한다
+ const networkResp = await fetch(event.request);
+ return networkResp;
+ } catch (error) {
+ // 인터넷 연결이 불안정할 경우, offline 페이지를 보여준다
+ const cache = await caches.open(CACHE);
+ const cachedResp = await cache.match(offlineFallbackPage);
+ return cachedResp;
+ }
+ })(),
+ );
+ }
+});
+
+// SW 활성화
+// 업데이트 할 때마다 caching 을 확인하고 promise 를 동작함
+self.addEventListener("activate", (event) => {
+ const cacheWhiteList = [];
+ cacheWhiteList.push(CACHE);
+
+ event.waitUntil(
+ caches.keys().then((cachesNames) =>
+ Promise.all(
+ cachesNames.map((cacheName) => {
+ if (!cacheWhiteList.includes(cacheName)) return caches.delete(cacheName);
+ }),
+ ),
+ ),
+ );
+});
diff --git a/tsconfig.json b/tsconfig.json
index 10997606..1139b4a3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -23,7 +23,7 @@
},
"include": [
"**/*.ts",
- "**/*.tsx", "public/mockServiceWorker.js",
+ "**/*.tsx", "public/mockServiceWorker.js", "public/pwaServiceWorker.js",
],
"exclude": [
"node_modules"