
Apa itu PWA?
Source:
web.dev/learn/pwa/progressive-web-apps/
Syarat Membuat PWA
- Icon Blog Anda dengan Format
.png
dengan ukuran512x512
Piksel. - 5 Screenshot halaman Web/Blog Anda dengan Format
.png
. - Mempunyai Akun Github.
- Menggunakan DNS Management Cloudflare.
- CNAME yang Terhubung dengan Blog Blogger sudah mengaktifkan Awan Proxy.
Mengunggah Icon di Github
- Siapkan Icon Web/Blog Anda dalam ukuran
512x512
dengan Format.png
, Ganti Nama Icon menjadiandroid-icon-512x512.png
. - Buka Website https://favicon-generator.org dan Unggah Icon Situs Anda.
- Unduh Icon yang sudah di Generate.
- Hapus File yang tidak diperlukan seperti
browserconfig.xml
danmanifest.json
. - Buka Github, Lalu buat Repository baru dan berikan Nama seperti icon-pagodaimedia.
- Unggah Semua File termasuk File aslinya yaitu
android-icon-512x512.png
dan jika dihitung maka semua file yang diunggah Berjumlah 26, lalu klik Commit.
Unggah Screenshot di Github
- Siapkan 5 Screenshot Halaman Web/Blog Anda.
- Berikan Nama 5 File tersebut menjadi :
- Unggah File di Repository yang sama seperti sebelumnya, Lalu klik Commit.
scr1.png
scr2.png
scr3.png
scr4.png
scr5.png
Membuat Worker di Cloudflare
- main-pagodaimedia
- manifest-pagodaimedia
- serviceworker-pagodaimedia
- offline-pagodaimedia
Main Worker
- Masuk ke Dashboard Cloudflare Anda.
- Pada Menu pada bilah Kiri Dashboard, Pilih Workers.
- Buat Workers baru dan berikan nama
main-pagodaimedia
. - Hapus Semua Script Bawaan dan ganti dengan Script Dibawah :
Pada nama Workers, Ganti main-pagodaimedia
menjadi main-namasituskamu
.
addEventListener("fetch", event => { event.respondWith(handleRequest(event)) }) //const BUCKET_NAME = "main" const BUCKET_URL = `https://cdn.statically.io/gh/pagodaimedia/icons` async function serveAsset(event) { const url = new URL(event.request.url) const cache = caches.default let response = await cache.match(event.request) if (!response) { response = await fetch(`${BUCKET_URL}${url.pathname}`) const headers = { "cache-control": "public, max-age=14400" } response = new Response(response.body, { ...response, headers }) event.waitUntil(cache.put(event.request, response.clone())) } return response } async function handleRequest(event) { if (event.request.method === "GET") { let response = await serveAsset(event) if (response.status > 399) { response = new Response(response.statusText, { status: response.status }) } return response } else { return new Response("Method not allowed", { status: 405 }) } }
Pada bagian yang ditandai yaitu:
pagodaimedia
ganti menjadi Username Github kalian dan icon-pagodaimedia
ganti menjadi Nama Repository Github anda.
Manifest.json
- Buat Workers baru dan berikan nama
manifest-pagodaimedia
. - Hapus Semua Script Bawaan dan ganti dengan Script Dibawah :
Pada nama Workers, Ganti manifest-pagodaimedia
menjadi manifest-namasituskamu
.
addEventListener("fetch", event => { const data = { name: "Pagodaimedia", short_name: "Pagodaimedia", description: "Pagodaimedia Adalah Website Yang Menyajikan Informasi Bermanfaat, Terbaru Dan Informasi Menarik Lainnya.", display: "standalone", prefer_related_applications: false, start_url: "\/?utm_source=homescreen", scope: "\/", background_color: "#2196f3", theme_color: "#2196f3", icons: [ { src: "\/main\/android-icon-512x512.png", sizes: "512x512", type: "image\/png", density: "4.0", purpose: "any maskable" }, { src: "\/main\/android-icon-192x192.png", sizes: "192x192", type: "image\/png", density: "4.0", purpose: "any maskable" }, { src: "\/main\/apple-icon-144x144.png", sizes: "144x144", type: "image\/png", density: "3.0", purpose: "any maskable" }, { src: "\/main\/android-icon-96x96.png", sizes: "96x96", type: "image\/png", density: "2.0", purpose: "any maskable" }, { src: "\/main\/android-icon-72x72.png", sizes: "72x72", type: "image\/png", density: "1.5", purpose: "any maskable" }, { src: "\/main\/android-icon-48x48.png", sizes: "48x48", type: "image\/png", density: "1.0", purpose: "any maskable" }, { src: "\/main\/android-icon-36x36.png", sizes: "36x36", type: "image\/png", density: "0.75", purpose: "any maskable" } ], shortcuts: [ { name: "Pagodaimedia", short_name: "Pagodaimedia", description: "Pagodaimedia Adalah Website Yang Menyajikan Informasi Bermanfaat, Terbaru Dan Informasi Menarik Lainnya.", url: "\/?utm_source=homescreen", icons: [ { src: "\/main\/android-icon-192x192.png", sizes: "192x192" } ] }, { name: "Pagodaimedia", short_name: "Pagodaimedia", description: "Pagodaimedia Adalah Website Yang Menyajikan Informasi Bermanfaat, Terbaru Dan Informasi Menarik Lainnya.", url: "\/search?utm_source=homescreen", icons: [ { src: "\/main\/android-icon-192x192.png", sizes: "192x192" } ] }, { name: "Pagodaimedia", short_name: "Pagodaimedia", description: "Pagodaimedia Adalah Website Yang Menyajikan Informasi Bermanfaat, Terbaru Dan Informasi Menarik Lainnya.", url: "\/search\/label\/Widgets?utm_source=homescreen", icons: [ { src: "\/main\/android-icon-192x192.png", sizes: "192x192" } ] } ], screenshots: [ { src: "\/main\/scr1.png", type: "image\/png", sizes: "540x720" }, { src: "\/main\/scr2.png", type: "image\/png", sizes: "540x720" }, { src: "\/main\/scr3.png", type: "image\/png", sizes: "540x720" }, { src: "\/main\/scr4.png", type: "image\/png", sizes: "540x720" }, { src: "\/main\/scr5.png", type: "image\/png", sizes: "540x720" } ], serviceworker: { src: "\/sw.js" } } const json = JSON.stringify(data, null, 2) return event.respondWith( new Response(json, { headers: { "content-type": "application/json;charset=UTF-8" } }) ) })
Pada bagian yang ditandai, Ganti sesuai keinginan anda.
Lalu klik Save and Deploy.ServiceWorker
- Buat Workers baru dan berikan nama
serviceworker-pagodaimedia
. - Hapus Semua Script Bawaan dan ganti dengan Script Dibawah :
Pada nama Workers, Ganti serviceworker-pagodaimedia
menjadi serviceworker-namasituskamu
.
const js = ` importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-sw.js'); if (workbox) { workbox.core.skipWaiting(); workbox.core.clientsClaim(); workbox.core.setCacheNameDetails({ prefix: 'thn-sw', suffix: 'v22', precache: 'install-time', runtime: 'run-time' }); const FALLBACK_HTML_URL = '/offline.html'; const version = workbox.core.cacheNames.suffix; workbox.precaching.precacheAndRoute([{url: FALLBACK_HTML_URL, revision: null},{url: '/manifest.json', revision: null},{url: '/main/favicon.ico', revision: null}]); workbox.routing.setDefaultHandler(new workbox.strategies.NetworkOnly()); workbox.routing.registerRoute( new RegExp('.(?:css|js|png|gif|jpg|svg|ico)$'), new workbox.strategies.CacheFirst({ cacheName: 'images-js-css-' + version, plugins: [ new workbox.expiration.ExpirationPlugin({ maxAgeSeconds: 60 * 24 * 60 * 60, maxEntries:200, purgeOnQuotaError: true }) ], }),'GET' ); workbox.routing.setCatchHandler(({event}) => { switch (event.request.destination) { case 'document': return caches.match(FALLBACK_HTML_URL); break; default: return Response.error(); } }); self.addEventListener('activate', function(event) { event.waitUntil( caches .keys() .then(keys => keys.filter(key => !key.endsWith(version))) .then(keys => Promise.all(keys.map(key => caches.delete(key)))) ); }); } else { console.log('Oops! Workbox did not load'); } ` async function handleRequest(request) { return new Response(js, { headers: { "content-type": "application/javascript;charset=UTF-8", }, }) } addEventListener("fetch", event => { return event.respondWith(handleRequest(event.request)) })
- Script diatas telah menggunakan Workbox, sehingga dapat men-cache HTML, CSS, JS, dan file statis apa pun.
- Lalu klik Save and Deploy.
Offline
- Buat Workers baru dan berikan nama
offline-pagodaimedia
. - Hapus Semua Script Bawaan dan ganti dengan Script Dibawah :
Pada nama Workers, Ganti offline-pagodaimedia
menjadi offline-namasituskamu
.
const html = `<!DOCTYPE html> <html> <head> <!--[ Meta Tags ]--> <title>Oops, You're Offline!</title> <meta charset='UTF-8'/> <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0' name='viewport'/> <meta content='IE=edge' http-equiv='X-UA-Compatible'/> <!--[ Theme Color ]--> <meta content='#2196f3' name='theme-color'/> <meta content='#2196f3' name='msapplication-navbutton-color'/> <meta content='#2196f3' name='apple-mobile-web-app-status-bar-style'/> <meta content='true' name='apple-mobile-web-app-capable'/> <!--[ Favicon ]--> <link href='/main/apple-icon-120x120.png' rel='apple-touch-icon' sizes='120x120'/> <link href='/main/apple-icon-152x152.png' rel='apple-touch-icon' sizes='152x152'/> <link href='/main/favicon-32x32.png' rel='icon' sizes='32x32' type='image/png'/> <link href='/main/favicon-96x96.png' rel='icon' sizes='96x96' type='image/png'/> <link href='/main/favicon-16x16.png' rel='icon' sizes='16x16' type='image/png'/> <link href='/main/favicon.ico' rel='icon' type='image/x-icon'/> <link href='/main/favicon.ico' rel='shortcut icon' type='image/x-icon'/> <!--[ Stylesheet ]--> <style>/*<![CDATA[*/ /* Merriweather - Font */ @font-face{font-family: 'Merriweather'; font-style: italic; font-weight: 300; font-display: swap; src: local('Merriweather-LightItalic'), url(https://fonts.gstatic.com/s/merriweather/v22/u-4l0qyriQwlOrhSvowK_l5-eR7lXff4jvw.woff2) format('woff2'), url(https://fonts.gstatic.com/s/merriweather/v22/u-4l0qyriQwlOrhSvowK_l5-eR7lXcf8.woff) format('woff')} @font-face{font-family: 'Merriweather'; font-style: italic; font-weight: 700; font-display: swap; src: url(https://fonts.gstatic.com/s/merriweather/v22/u-4l0qyriQwlOrhSvowK_l5-eR7NWPf4jvw.woff2) format('woff2'), url(https://fonts.gstatic.com/s/merriweather/v22/u-4l0qyriQwlOrhSvowK_l5-eR71Wsf8.woff) format('woff')} @font-face{font-family: 'Merriweather'; font-style: italic; font-weight: 900; font-display: swap; src: url(https://fonts.gstatic.com/s/merriweather/v22/u-4l0qyriQwlOrhSvowK_l5-eR7NWPf4jvw.woff2) format('woff2'), url(https://fonts.gstatic.com/s/merriweather/v22/u-4l0qyriQwlOrhSvowK_l5-eR7NWMf8.woff) format('woff')} @font-face{font-family: 'Merriweather'; font-style: normal; font-weight: 300; font-display: swap; src: url(https://fonts.gstatic.com/s/merriweather/v22/u-4n0qyriQwlOrhSvowK_l521wRZWMf6.woff2) format('woff2'), url(https://fonts.gstatic.com/s/merriweather/v22/u-4n0qyriQwlOrhSvowK_l521wRpXA.woff) format('woff')} @font-face{font-family: 'Merriweather'; font-style: normal; font-weight: 700; font-display: swap; src: url(https://fonts.gstatic.com/s/merriweather/v22/u-4n0qyriQwlOrhSvowK_l52xwNZWMf6.woff2) format('woff2'), url(https://fonts.gstatic.com/s/merriweather/v22/u-4n0qyriQwlOrhSvowK_l52xwNpXA.woff) format('woff')} @font-face{font-family: 'Merriweather'; font-style: normal; font-weight: 900; font-display: swap; src: url(https://fonts.gstatic.com/s/merriweather/v22/u-4n0qyriQwlOrhSvowK_l52_wFZWMf6.woff2) format('woff2'), url(https://fonts.gstatic.com/s/merriweather/v22/u-4n0qyriQwlOrhSvowK_l52_wFpXA.woff) format('woff')} /* Content */ body{background:#f1f3f6;color:#1f1f1f;font-family:'Merriweather',serif;font-weight:400;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body:focus{outline:none !important} .mainCont{margin:0 auto;position:fixed;left:0;top:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;padding:15px} .noIntPop{position:relative;overflow:hidden;text-align:center;padding:15px;border-radius:30px;background:#f1f3f6;box-shadow:inset 0 0 15px rgba(55, 84, 170, 0), inset 0 0 20px rgba(255, 255, 255, 0), 7px 7px 15px rgba(55, 84, 170, 0.15), -7px -7px 20px white, inset 0px 0px 4px rgba(255, 255, 255, 0.2)} .circle.t{top:-150px;right:-150px} .circle.b{bottom:-150px;left:-150px} .noIntCont{position:relative;z-index:1} .noIntIcon{padding:30px} .noConHead{font-weight:700;font-size:1.3rem} .noConDesc{font-size:16px;line-height:1.4em;padding-top:20px;font-weight:400;opacity:.8} .cta,.relCont{display:flex;justify-content:center;align-items:center} .relCont{padding:30px} .cta{width:66px;height:66px;background:#f1f3f6;outline:none;border:none;border-radius:690px;box-shadow:inset 0 0 15px rgba(55, 84, 170, 0), inset 0 0 20px rgba(255, 255, 255, 0), 7px 7px 15px rgba(55, 84, 170, 0.15), -7px -7px 20px white, inset 0px 0px 4px rgba(255, 255, 255, 0.2);transition:box-shadow 399ms ease-in-out} .cta:hover{box-shadow:inset 7px 7px 15px rgba(55, 84, 170, 0.15), inset -7px -7px 20px white, 0px 0px 4px rgba(255, 255, 255, 0.2)} .icon{content:'';width:25px;height:25px;display:inline-block} .iconB{content:'';width:50px;height:50px;display:inline-block} .icon.reload{background:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239dabc0' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='23 4 23 10 17 10'/><path d='M20.49 15a9 9 0 1 1-2.12-9.36L23 10'/></svg>") center / 25px no-repeat} .iconB.wifiOff{background:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231f1f1f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><line x1='1' y1='1' x2='23' y2='23'/><path d='M16.72 11.06A10.94 10.94 0 0 1 19 12.55'/><path d='M5 12.55a10.94 10.94 0 0 1 5.17-2.39'/><path d='M10.71 5.05A16 16 0 0 1 22.58 9'/><path d='M1.42 9a15.91 15.91 0 0 1 4.7-2.88'/><path d='M8.53 16.11a6 6 0 0 1 6.95 0'/><line x1='12' y1='20' x2='12.01' y2='20'/></svg>") center / 50px no-repeat} .circle{position:absolute;z-index:1;width:280px;height:280px;border-radius:50%;background-color:#f1f3f6;box-shadow:inset 8px 8px 12px #d1d9e6, inset -8px -8px 12px #f9f9f9} /*]]>*/</style> </head> <body> <div class='mainCont notranslate'> <div class='noIntPop'> <div class='circle t'></div> <div class='circle b'></div> <div class='noIntCont'> <div class='noIntIcon'> <i class='iconB wifiOff'></i> </div> <div class='noConHead'>Oops, You're Offline!</div> <div class='noConDesc'>It looks like your network connection isn't working right now.</div> <div class='relCont'> <button class='cta' onclick='window.location.reload()'> <i class='icon reload'></i> </button> </div> </div> </div> </div> </body> </html>` async function handleRequest(request) { return new Response(html, { headers: { "content-type": "text/html;charset=UTF-8", }, }) } addEventListener("fetch", event => { return event.respondWith(handleRequest(event.request)) })
Pada bagian yang ditandai, Ganti sesuai keinginan anda.
Lalu klik Save and Deploy.Membuat Router
- Kembali pada bagian Workers dan klik Add Route.
- Masukkan bidang seperti yang ditunjukkan pada tabel yang dibawah:
Route | Service | Environment |
---|---|---|
www.pagodaimedia.com/main/* | main-pagodaimedia | Production |
www.pagodaimedia.com/manifest.json | manifest-pagodaimedia | Production |
www.pagodaimedia.com/sw.js | serviceworker-pagodaimedia | Production |
www.pagodaimedia.com/offline.html | offline-pagodaimedia | Production |
Masukkan bidang sesuai url blog dan nama blog Anda.
www.pagodaimedia.com/main/android-icon-512x512.png
www.pagodaimedia.com/manifest.json
www.pagodaimedia.com/sw.js
www.pagodaimedia.com/offline.html
Masukkan bidang sesuai url blog Anda.
Membuka Blogger
- Masuk ke Dashboard Blogger.
- Buka bagian Tema.
- Klik Edit HTML.
- Tempel kode ini dibawah
<head>
atau<head>
<link href='/main/apple-icon-57x57.png' rel='apple-touch-icon' sizes='57x57'/> <link href='/main/apple-icon-60x60.png' rel='apple-touch-icon' sizes='60x60'/> <link href='/main/apple-icon-72x72.png' rel='apple-touch-icon' sizes='72x72'/> <link href='/main/apple-icon-76x76.png' rel='apple-touch-icon' sizes='76x76'/> <link href='/main/apple-icon-114x114.png' rel='apple-touch-icon' sizes='114x114'/> <link href='/main/apple-icon-120x120.png' rel='apple-touch-icon' sizes='120x120'/> <link href='/main/apple-icon-114x114.png' rel='apple-touch-icon' sizes='144x144'/> <link href='/main/apple-icon-152x152.png' rel='apple-touch-icon' sizes='152x152'/> <link href='/main/apple-icon-180x180.png' rel='apple-touch-icon' sizes='180x180'/> <link href='/main/android-icon-192x192.png' rel='icon' sizes='192x192' type='image/png'/> <link href='/main/favicon-32x32.png' rel='icon' sizes='32x32' type='image/png'/> <link href='/main/favicon-96x96.png' rel='icon' sizes='96x96' type='image/png'/> <link href='/main/favicon-16x16.png' rel='icon' sizes='16x16' type='image/png'/> <link href='/main/favicon.ico' rel='icon' type='image/x-icon'/> <meta content='#2196f3' name='msapplication-TileColor'/> <meta content='/main/ms-icon-144x144.png' name='msapplication-TileImage'/> <meta content='#2196f3' name='theme-color'/> <link href='/manifest.json' rel='manifest'/>
Pada bagian yang ditandai, Ganti sesuai kode warna pada manifest.json
.
- Sekarang ikuti langkah-langkah sesuai template Anda, yaitu Template AMP atau Template Non-AMP.
<head>
atau di atas </head>
.<script async='async' custom-element='amp-install-serviceworker' src='https://cdn.ampproject.org/v0/amp-install-serviceworker-0.1.js'/>
</body>
.
<amp-install-serviceworker data-iframe-src='/offline.html' layout='nodisplay' src='/sw.js'/>
</body>
.<script>/*<![CDATA[*/ /* Service Worker */ if('serviceWorker' in navigator){window.addEventListener('load',()=>{navigator.serviceWorker.register('/sw.js').then(registration=>{console.log('ServiceWorker registeration successful')}).catch(registrationError=>{console.log('ServiceWorker registration failed: ', registrationError)})})}; /*]]>*/</script>
Catatan
Referensi:
https://www.fineshopdesign.com/2022/03/how-to-build-progressive-web-app.html
https://web.dev/explore/progressive-web-apps