{"id":16180,"date":"2025-08-04T08:13:23","date_gmt":"2025-08-03T23:13:23","guid":{"rendered":"http:\/\/www.gisdeveloper.co.kr\/?p=16180"},"modified":"2025-08-07T08:54:11","modified_gmt":"2025-08-06T23:54:11","slug":"pwa-porgressive-web-app-%ea%b0%9c%eb%b0%9c-%ea%b0%80%ec%9d%b4%eb%93%9c","status":"publish","type":"post","link":"http:\/\/www.gisdeveloper.co.kr\/?p=16180","title":{"rendered":"PWA (Progressive Web APP) \uac1c\ubc1c \uac00\uc774\ub4dc"},"content":{"rendered":"<p>vite\ub85c \uad6c\uc131\ub41c \ubc14\ub2d0\ub77c \ud504\ub85c\uc81d\ud2b8\uac00 \uae30\uc900\uc774\ub2e4. \uba3c\uc800 index.html \ud30c\uc77c\uc5d0 manifest.json\uc5d0 \ub300\ud55c \uc5f0\uacb0\uc774 \ud544\uc694\ud558\ub2e4.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\">\r\n&lt;head>\r\n  &lt;link rel=\"manifest\" href=\"\/manifest.json\" \/>\r\n  &lt;link rel=\"icon\" href=\"\/icon_192x192.png\" \/>\r\n&lt;\/head>\r\n<\/pre>\n<p>\uc704\uc758 \ucf54\ub4dc\ub97c \ubcf4\uba74 \uc120\ud0dd\uc0ac\ud56d\uc774\uc9c0\ub9cc \uc544\uc774\ucf58\ub3c4 \uc9c0\uc815\ud588\ub2e4. \uc774 \uc544\uc774\ucf58 \uc5ed\uc2dc manifest \ub0b4\ubd80\uc5d0\uc11c \uc5b8\uae09\ub418\ub294\ub370, index.html\uc740 url\uc744 \ud1b5\ud574 \uc811\uc18d\ud588\uc744\ub54c \uc6f9\ube0c\ub77c\uc6b0\uc800\uc5d0 \ud45c\uc2dc\ub420 \uc544\uc774\ucf58\uc774\uace0 manifest\uc5d0\uc11c\ub294 \ubc14\ud0d5\ud654\uba74 \ub4f1\uc5d0 App\uc73c\ub85c \ub4f1\ub85d\ub420\ub54c \ud45c\uc2dc\ub418\ub294 \uc544\uc774\ucf58\uc774\ub2e4. mainfest.json \ud30c\uc77c\uc740 \ub2e4\uc74c\uacfc \uac19\uc73c\uba70 \uc704\uce58\ub294 public \ud3f4\ub354\uc774\ub2e4.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">\r\n{\r\n  \"name\": \"Vite PWA \uc608\uc81c\",\r\n  \"short_name\": \"VitePWA\",\r\n  \"description\": \"\uac04\ub2e8\ud55c Vite \uae30\ubc18 PWA \uc608\uc81c\",\r\n  \"start_url\": \"\/\",\r\n  \"display\": \"standalone\",\r\n  \"theme_color\": \"#ff0000\",\r\n  \"background_color\": \"#ff0000\",\r\n  \"icons\": [\r\n    {\r\n      \"src\": \"\/icon_192x192.png\",\r\n      \"sizes\": \"192x192\",\r\n      \"type\": \"image\/png\",\r\n      \"purpose\": \"any maskable\"\r\n    },\r\n    {\r\n      \"src\": \"\/icon_512x512.png\",\r\n      \"sizes\": \"512x512\",\r\n      \"type\": \"image\/png\",\r\n      \"purpose\": \"any maskable\"\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>\uc5ec\uae30\uae4c\uc9c0\ub9cc \ud574\ub3c4 PWA\ub85c\uc368 \ub0b4\uac00 \uc6d0\ud558\ub294 \uc544\uc774\ucf58\uacfc \uc81c\ubaa9 \ub4f1\uc73c\ub85c \uc124\uc815\ub41c \uc571\uc73c\ub85c \uc124\uce58\ud560 \uc218 \uc788\uac8c \ub41c\ub2e4. \uc5ec\uae30\uc5d0 \ub354\ud574&#8230;. main.js \ud30c\uc77c\uc5d0 \ub300\ud55c \ub0b4\uc6a9\uc774\ub2e4. \ud06c\uac8c 3\uac00\uc9c0 \ub0b4\uc6a9\uc774\ub2e4. DOM \uad6c\uc131, \uc11c\ube44\uc2a4\uc6cc\ucee4 \uad6c\uc131, \uc571 \uc124\uce58 UI\uc774\ub2e4. \uba3c\uc800 DOM \uad6c\uc131\uc740 \ub2e4\uc74c\ucc98\ub7fc \ud588\ub2e4.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">\r\nimport '.\/style.css'\r\n\r\ndocument.querySelector('#app').innerHTML = \/*html*\/ `\r\n  <h1>Vite PWA \uc608\uc81c<\/h1>\r\n  <img decoding=\"async\" src=\".\/icon_512x512.png\" \/>\r\n  <p>\uc774\uac83\uc740 \uc0ac\uc6a9\uc790 \uc815\uc758 Service Worker\ub97c \uc0ac\uc6a9\ud55c PWA\uc785\ub2c8\ub2e4.<\/p>\r\n  <p>\uc624\ud504\ub77c\uc778\uc5d0\uc11c\ub3c4 \ub3d9\uc791\ud558\uba70, \uc124\uce58 \uac00\ub2a5\ud569\ub2c8\ub2e4!<\/p>\r\n  <button id=\"installButton\" style=\"display: none;\">\uc571 \uc124\uce58\ud558\uae30<\/button>\r\n  <p id=\"installMessage\"><\/p>\r\n`;\r\n<\/pre>\n<p>\uc11c\ube44\uc2a4 \uc6cc\ud06c \uad6c\uc131\uc740 \ub2e4\uc74c\ucc98\ub7fc \ud588\ub2e4.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">\r\nwindow.addEventListener('load', () => {\r\n  navigator.serviceWorker.register('\/sw.js')\r\n    .then(registration => {\r\n      console.log('Service Worker \ub4f1\ub85d \uc131\uacf5:', registration.scope);\r\n    })\r\n    .catch(error => {\r\n      console.error('Service Worker \ub4f1\ub85d \uc2e4\ud328:', error);\r\n    });\r\n});\r\n<\/pre>\n<p>\uc571 \uc124\uce58 UI\uc5d0 \ub300\ud55c \ucf54\ub4dc\ub294 \ub2e4\uc74c\ucc98\ub7fc \ud588\ub2e4.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">\r\nlet deferredPrompt;\r\n\/\/const installButton = document.getElementById('installButton');\r\n\/\/const installMessage = document.getElementById('installMessage');\r\n\r\nwindow.addEventListener('beforeinstallprompt', (e) => {\r\n  \/\/ e.preventDefault();\r\n  deferredPrompt = e;\r\n  installButton.style.display = 'block';\r\n  installMessage.textContent = '\uc571\uc744 \ubc14\ud0d5\ud654\uba74\uc5d0 \uc124\uce58\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4!';\r\n});\r\n\r\ninstallButton.addEventListener('click', async () => {\r\n  if (deferredPrompt) {\r\n    deferredPrompt.prompt();\r\n    const { outcome } = await deferredPrompt.userChoice;\r\n    installMessage.textContent = outcome === 'accepted'\r\n      ? '\uc571 \uc124\uce58\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4!'\r\n      : '\uc571 \uc124\uce58\uac00 \ucde8\uc18c\ub418\uc5c8\uc2b5\ub2c8\ub2e4.';\r\n    deferredPrompt = null;\r\n    installButton.style.display = 'none';\r\n  }\r\n});\r\n\r\nwindow.addEventListener('appinstalled', () => {\r\n  installMessage.textContent = '\uc571\uc774 \uc131\uacf5\uc801\uc73c\ub85c \uc124\uce58\ub418\uc5c8\uc2b5\ub2c8\ub2e4!';\r\n  installButton.style.display = 'none';\r\n});\r\n<\/pre>\n<p>\uc11c\ube44\uc2a4\uc6cc\ucee4\uc5d0 \ub300\ud55c sw.js \ud30c\uc77c\uc758 \ucf54\ub4dc\ub294 \ub2e4\uc74c\uacfc \uac19\uc73c\uba70 \ud30c\uc77c \uc704\uce58\ub294 public\uc774\ub2e4. \uc774 \uc11c\ube44\uc2a4\uc6cc\ucee4\ub294 \uce90\uc26c\uc5d0 \ub300\ud55c \uae30\ub2a5\uc774\ub2e4.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">\r\nconst CACHE_NAME = 'vite-pwa-cache-v1';\r\nconst urlsToCache = [\r\n  '\/',\r\n  '\/index.html',\r\n  '\/style.css',\r\n  '\/icon_192x192.png',\r\n  '\/icon_512x512.png'\r\n];\r\n\r\n\/\/ \uc124\uce58 \uc2dc \uce90\uc2f1\r\nself.addEventListener('install', event => {\r\n  event.waitUntil(\r\n    caches.open(CACHE_NAME)\r\n      .then(cache => {\r\n        console.log('\uce90\uc2dc \uc5f4\uae30 \uc131\uacf5');\r\n        return cache.addAll(urlsToCache);\r\n      })\r\n  );\r\n});\r\n\r\n\/\/ \uc694\uccad \ucc98\ub9ac (CacheFirst \uc804\ub7b5)\r\nself.addEventListener('fetch', event => {\r\n  const dest = event.request.destination;\r\n  \/\/ if (event.request.destination === 'document') {\r\n    event.respondWith(\r\n      caches.match(event.request)\r\n        .then(cachedResponse => {\r\n          \/\/ \uce90\uc2dc\uc5d0 \uc788\uc73c\uba74 \uce90\uc2dc\ub41c \uc751\ub2f5 \ubc18\ud658\r\n          if (cachedResponse) {\r\n            console.log(dest + ' (' + event.request.url + ') from cache');\r\n            return cachedResponse;\r\n          }\r\n          \/\/ \uce90\uc2dc\uc5d0 \uc5c6\uc73c\uba74 \ub124\ud2b8\uc6cc\ud06c \uc694\uccad\r\n          return fetch(event.request)\r\n            .then(networkResponse => {\r\n              \/\/ \ub124\ud2b8\uc6cc\ud06c \uc751\ub2f5\uc744 \uce90\uc2dc\uc5d0 \uc800\uc7a5\r\n              console.log(dest + ' (' + event.request.url + ') from fetch');\r\n              return caches.open(CACHE_NAME).then(cache => {\r\n                cache.put(event.request, networkResponse.clone());\r\n                return networkResponse;\r\n              });\r\n            })\r\n            .catch(() => {\r\n              onsole.log(dest + ' (' + event.request.url + ') failed');\r\n              \/\/ \ub124\ud2b8\uc6cc\ud06c \uc694\uccad \uc2e4\ud328 \uc2dc (\uc624\ud504\ub77c\uc778 \ub4f1)\r\n              return new Response('\uc624\ud504\ub77c\uc778 \uc0c1\ud0dc\uc785\ub2c8\ub2e4. \uce90\uc2dc\ub3c4 \uc5c6\uc2b5\ub2c8\ub2e4.', {\r\n                status: 503,\r\n                statusText: 'Service Unavailable'\r\n              });\r\n            });\r\n        })\r\n    );\r\n  \/\/ }\r\n});\r\n\r\n\/\/ \uc694\uccad \ucc98\ub9ac (NetworkFirst \uc804\ub7b5)\r\nself.addEventListener('_____fetch', event => {\r\n  console.log(event.request.destination);\r\n  \/\/ if (event.request.destination === 'document') {\r\n    event.respondWith(\r\n      fetch(event.request)\r\n        .then(response => {\r\n          \/\/ \ub124\ud2b8\uc6cc\ud06c \uc751\ub2f5\uc744 \uce90\uc2dc\uc5d0 \uc800\uc7a5\r\n          return caches.open(CACHE_NAME).then(cache => {\r\n            console.log(event.request.url + ' from fetch');\r\n            cache.put(event.request, response.clone());\r\n            return response;\r\n          });\r\n        })\r\n        .catch(() => {\r\n          \/\/ \ub124\ud2b8\uc6cc\ud06c \uc2e4\ud328 \uc2dc \uce90\uc2dc\uc5d0\uc11c \uac00\uc838\uc624\uae30\r\n          console.log(event.request.url + ' from cache');\r\n          return caches.match(event.request);\r\n        })\r\n    );\r\n  \/\/ }\r\n});\r\n\r\n\/\/ \uce90\uc2dc \uc815\ub9ac\r\nself.addEventListener('activate', event => {\r\n  const cacheWhitelist = [CACHE_NAME];\r\n  event.waitUntil(\r\n    caches.keys().then(cacheNames => {\r\n      return Promise.all(\r\n        cacheNames.map(cacheName => {\r\n          if (!cacheWhitelist.includes(cacheName)) {\r\n            return caches.delete(cacheName);\r\n          }\r\n        })\r\n      );\r\n    })\r\n  );\r\n  console.log('\uce90\uc2dc \uc815\ub9ac \uc131\uacf5');\r\n});\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>vite\ub85c \uad6c\uc131\ub41c \ubc14\ub2d0\ub77c \ud504\ub85c\uc81d\ud2b8\uac00 \uae30\uc900\uc774\ub2e4. \uba3c\uc800 index.html \ud30c\uc77c\uc5d0 manifest.json\uc5d0 \ub300\ud55c \uc5f0\uacb0\uc774 \ud544\uc694\ud558\ub2e4. &lt;head> &lt;link rel=&#8221;manifest&#8221; href=&#8221;\/manifest.json&#8221; \/> &lt;link rel=&#8221;icon&#8221; href=&#8221;\/icon_192x192.png&#8221; \/> &lt;\/head> \uc704\uc758 \ucf54\ub4dc\ub97c \ubcf4\uba74 \uc120\ud0dd\uc0ac\ud56d\uc774\uc9c0\ub9cc \uc544\uc774\ucf58\ub3c4 \uc9c0\uc815\ud588\ub2e4. \uc774 \uc544\uc774\ucf58 \uc5ed\uc2dc manifest \ub0b4\ubd80\uc5d0\uc11c \uc5b8\uae09\ub418\ub294\ub370, index.html\uc740 url\uc744 \ud1b5\ud574 \uc811\uc18d\ud588\uc744\ub54c \uc6f9\ube0c\ub77c\uc6b0\uc800\uc5d0 \ud45c\uc2dc\ub420 \uc544\uc774\ucf58\uc774\uace0 manifest\uc5d0\uc11c\ub294 \ubc14\ud0d5\ud654\uba74 \ub4f1\uc5d0 App\uc73c\ub85c \ub4f1\ub85d\ub420\ub54c \ud45c\uc2dc\ub418\ub294 \uc544\uc774\ucf58\uc774\ub2e4. mainfest.json \ud30c\uc77c\uc740 \ub2e4\uc74c\uacfc \uac19\uc73c\uba70 \uc704\uce58\ub294 public \ud3f4\ub354\uc774\ub2e4. &hellip; <\/p>\n<p class=\"link-more\"><a href=\"http:\/\/www.gisdeveloper.co.kr\/?p=16180\" class=\"more-link\">\ub354 \ubcf4\uae30<span class=\"screen-reader-text\"> &#8220;PWA (Progressive Web APP) \uac1c\ubc1c \uac00\uc774\ub4dc&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-16180","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=\/wp\/v2\/posts\/16180","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=16180"}],"version-history":[{"count":8,"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=\/wp\/v2\/posts\/16180\/revisions"}],"predecessor-version":[{"id":16193,"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=\/wp\/v2\/posts\/16180\/revisions\/16193"}],"wp:attachment":[{"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=16180"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=16180"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.gisdeveloper.co.kr\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=16180"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}