Progressive Web App Blogger - Pernahkah Anda melihat sebuah Pop Up notifikasi "Add to Home Screen" pada blog yang sedang Anda kunjungi? Jika iya, itu artinya anda sedang melihat PWA dari blog tersebut bekerja.
Apa itu PWA?
PWA adalah singkatan dari Progressive Web App, sebuah aplikasi yang dibangun dengan melakukan optimasi pada sebuah website. PWA merupakan salah satu solusi untuk membuat akses website menjadi lebih cepat
Beberapa web besar seperti Pinterest, twitter dan facebook juga menggunakan PWA untuk kemudahan penggunanya. Seperti namanya PWA adalah aplikasi website progresif, yang tentu berbeda dengan Web App, dan juga Mobile App.
Lalu, apakah perbedaan ketiganya? Berikut ini karakteristik yang dimiliki oleh masing-masing aplikasi:
Web App
- Dibuat dengan mengandalkan browser dan cross platform
- Update berjalan otomatis
- Hanya berjalan dalam kondisi online
- Fitur terbatas
- Biaya development murah
Aplikasi Mobile
- Dibuat untuk platform tertentu menggunakan bahasa pemrograman khusus, seperti Swift untuk iOS dan Java/Kotlin untuk Android
- Update harus dilakukan manual
- Bisa berjalan dalam kondisi offline
- Fitur lengkap yang menjamin kenyamanan pengguna
- Biaya development cenderung mahal
Progressive Web App
- Dibuat mengandalkan browser dan cross platform
- Update berjalan otomatis
- Bisa berjalan dalam kondisi offline
- Menghadirkan UX yang lebih intuitif dari web app
- Biaya development murah
Cara Membuat Progressive Web App (PWA) pada Blogger
Untuk membuat Progressive Web App, Anda membutuhkan beberapa fitur tambahan seperti service workers yang fungsinya membuat Blog tetap berjalan dalam keadaan offline. Kemudian Push Notification yang tampil ketika Blog dibuka. Anda juga bisa menambahkan notifikasi/menu 'Add to Home Screen" sehingga pengunjung bisa secara langsung menginstall aplikasi dari menu browser.
Mungkin tutorial ini agak panjang dan membingungkan. Pastikan Anda mengikuti langkah - langkah yang saya jelaskan dengan benar tanpa ada yang terlewat.
Penting! Untuk bisa membuat PWA ini blog Anda harus sudah menggunakan domain TLD yang sudah terhubung ke layanan Cloudflare dan tidak akan bekerja pada domain blogspot.com
Persiapan
Sebelum melanjutkan Anda wajib menyiapkan beberapa bahan dibawah ini :
- Logo blog format
.png
dengan ukuran512x512
pixel - 5 Screenshot blog format
.png
- Buat akun Github jika belum punya
- DNS Management terhubung dengan Cloudflare
Upload Icon ke Github
- Ubah nama logo yang sebelumnya sudah Anda siapkan dengan
android-icon-512x512.png
- Generate favicon online di favicon-generator.org
- Download favicon yang berhasil di generate
- Extract file
.zip
dan hapus file yang tidak terpakai yaitubrowserconfig.xml
danmanifest.json
- Buka akun Github Anda dan buat Repository baru misalnya
icon-kuymase
- Upload semua favicon termasuk icon original
android-icon-512x512.png
yang berjumlah sekitar 26 file pada main branch tanpa folder apapun (hanya icon) - Jika sudah silahkan Simpan atau
Commit
Upload Screenshot ke Github
- Siapkan 5 Screenshot tampilan blog Anda yang sebelumnya
- Rename masing - masing file secara berurutan menjadi :
scr1.png
scr2.png
scr3.png
scr4.png
scr5.png
- Buka repository Github yang sama dengan sebelumnya
- Upload semua file tanpa folder sama seperti sebelumnya
- Silahkan
Commit changes
Membuat Workers pada Cloudflare
Ada 4 Workers yang akan kita buat. Untuk memudahkan saat ROUTE nantinya, saya sarankan Anda untuk menamai masing - masing workers seperti berikut :
main-kuymase
manifest-kuymase
serviceworker-kuymase
offline-kuymase
Ganti kuymase
dengan nama blog Anda
Main Workers
- Login ke Akun Cloudflare Anda
- Buka Domain aktif
- Pilih
Workers
kemudian Manage Workers - Buat workers baru dan rename menjadi
main-bloganda
contohmain-kuymase
- Hapus semua script default dan ganti dengan script dibawah ini :
- Kemudian klik Save and Deploy
addEventListener("fetch", event => {
event.respondWith(handleRequest(event))
})
//const BUCKET_NAME = "main"
const BUCKET_URL = `https://cdn.statically.io/gh/kuymase/icon-kuymase`
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 })
}
}
Silahkan ganti github-user dengan username Github Anda
Ubah kuymase/icon-kuymase dengan nama Repository Anda
Manifest.json
- Buat workers baru dengan cara yang sama dan rename menjadi
manifest-namablog
contoh:manifest-kuymase
- Hapus semua script default dan ganti dengan dibawah ini :
- Ubah yang saya tandai sesuai keinginan Anda termasuk warna
- Kemudian Save and Deploy
addEventListener("fetch", event => {
const data = {
name: "KuyMase Blog",
short_name: "KuyMase Blog",
description: "Install KuyMase Blog App right Now!",
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: "KuyMase Home",
short_name: "KuyMase Home",
description: "Temukan tutorial dan informasi menarik seputar blogging dan teknologi.",
url: "\/?utm_source=homescreen",
icons: [
{
src: "\/main\/android-icon-192x192.png",
sizes: "192x192"
}
]
},
{
name: "Blogger Widgets",
short_name: "Blogger Widgets",
description: "Tutorial widget blogger.",
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"
}
})
)
})
Service Worker
- Buat lagi workers baru dan rename menjadi
serviceworker-namablog
contoh:serviceworker-kuymase
- Ganti script default dengan script dibawah ini :
- Kemudian klik Save dan Deploy
const js = `
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/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))
})
Offline
- Sekali lagi buat workers baru dengan nama
offline-namablog
contoh:offline-kuymase
- Ganti script default dengan script dibawah ini :
- Jika sudah silahkan klik Save anda Deploy
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))
})
Bagian yang saya tandai bisa Anda ubah
Membuat Routes
- Sekarang kembali pada tab Workers
- Silahkan klik tombol Add route
- Kemudian isi kolom sesuai tabel berikut :
- Berikutnya silahkan lakukan pengetesan masing - masing url, misalnya :
Sesuaikan URL dan nama workers degan milik Anda
Route | Service | Environment |
---|---|---|
www.kuymase.com/main/* | main-kuymase | production |
www.kuymase.com/manifest.json | manifest-kuymase | production |
www.kuymase.com/sw.js | serviceworker-kuymase | production |
www.kuymase.com/offline.html | offline-kuymase | production |
www.kuymase.com/main/android-icon-512x512.png
www.kuymase.com/manifest.json
www.kuymase.com/sw.js
www.kuymase.com/offline.html
Jika semua URL bisa dibuka tanpa error, maka konfigurasi pada Cloudflare sudah selesai.
Edit Template Blog
Langkah berikutnya adalah memasang beberapa kode htnl dan javascript pada blog Anda. Silahkan ikuti langkah berikut
- Buka Dashboard blogger Anda
- Pilih Tema kemudian Edit HTML
- Paste kode dibawah ini di dalam tag
<head>
atau<head>
dan hapus jika ada kode yang sama.
<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'/>
Bagian color silahkan samakan dengan yang sebelumnya Anda pilih pada manifest.json
Selanjutnya ikuti langkah dibawah ini sesuai dengan jenis template yang Anda gunakan.
Khusus AMP Template
Jika Anda menggunakan temlate AMP silahkan ikuti langkah berikut :
- Tambahkan Serviceworker JS khusus AMP dibawah ini didalam tag
<head>
atau<head>
. - Kemudian kode dibawah ini tepat sebelum tag
</body>
atau</body>
. - Kemudian Simpan
<script async='async' custom-element='amp-install-serviceworker' src='https://cdn.ampproject.org/v0/amp-install-serviceworker-0.1.js'/>
<amp-install-serviceworker data-iframe-src='/offline.html' layout='nodisplay' src='/sw.js'/>
Template Standar Non-AMP
Jika Anda menggunakan template standar atau Non-AMP ikuti langkah berikut :
- Copy kode berikut dan paste tepat sebelum tag
</body>
atau</body>
. - Jika sudah silahkan Simpan
<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>
Kesimpulan
Sekian cara membuat PWA (Progressive Web App) blogger. Semoga tutorial diatas bermanfaat untuk Anda Jika ada pertanyaan silahkan tinggalkan komentar.
Original Post :
https://www.fineshopdesign.com/2022/03/how-to-build-progressive-web-app.html