Chào các bạn đến với NLD Blog. Đã bao giờ các bạn truy cập vào một Website hay một Blog và các bạn thấy hiện trên màn hình nút "Add To Home Screen" chưa? Nếu bạn đã thấy và đang muốn tìm cách để thêm nó vào Blog/Website của mình mà chưa biết cách thì bài viết này mình sẽ hướng dẫn các bạn Cách tạo Progressive Web App (PWA) cho Blogger/Website.
Cách tạo Progressive Web App (PWA) cho Blogger/Website
Trong bài viết này mình sẽ hướng dẫn các bạn cách xây dựng Progressive Web App hay còn gọi tắt là PWA một cách dễ ràng với Blogger.
Trước khi bắt đầu chúng ta hãy cũng nhau tìm hiểu một chút về Progressive Web App hay PWA là gì và nó quan trọng như thế nào đối với Website.
PWA là gì?
Progressive Web App (PWA) là sự kết hợp hoàn hảo giữa web và ứng dụng, giúp cho các ứng dụng sử dụng trên trang web có những tính năng như một ứng dụng thực sự.
Nhờ vào tính năng của service worker, manifest và https, PWA có thể hoạt động offline ngay cả khi không có mạng.
Bên cạnh đó, PWA còn hỗ trợ gửi những thông báo liên quan, góp phần tăng hiệu quả hoạt động marketing.
Tóm lại, PWA là ứng dụng có thể cung cấp những tính năng bổ sung dựa trên các thiết bị hỗ trợ, cung cấp khả năng ngoại tuyến, đẩy thông báo, có giao diện và tốc độ tương đương với ứng dụng Native và lưu trữ cục bộ các tài nguyên.
Tại sao PWAs lại quan trọng?
Mặc dù các ngôn ngữ lập trình như PHP, Java, Javascript cung cấp những bản cập nhật giúp tăng tốc rất nhiều. Nhưng những trang web được tạo ra vẫn có độ trễ và thời gian chờ đợi lên tới vài giây. Thậm chí hơn nếu có nhiều chức năng.
Với việc người dùng càng ngày càng thiếu kiên nhận, việc chờ đợi 3-5s (hiện nay là tốc độ cực kỳ tốt) cũng có thể sẽ không còn ưu thế trong 1 vài năm tới. Khi đó PWA sẽ nhanh chóng trở thành một giải pháp hàng đầu cho các lập trình viên web.
Bằng cách tạo ra sự kết hợp giữa trang web và ứng dụng di động, tận dụng được lợi thế tốc độ của PWA, các công ty này có thể sớm lôi kéo được khách hàng tốt hơn đối thủ.
Đúng thế, tạo ra một giải pháp giúp khách hàng truy cập nhanh với tốc độ gần bằng 0 thì tại sao khách hàng lại muốn truy cập trang web mà phải chờ đợi mỗi lần load chứ.
Thậm chí, tốc độ là yếu tố hàng đầu khi nói đến SEO, gia tăng chuyển đổi và tạo ra trải nghiệm người dùng tốt hơn. Sử dụng PWA bạn sẽ tạo ra được trải nghiệm "WOW" - Đây là thứ mà mọi lập trình viên đều muốn.
Làm thế nào để xây dựng PWA cho Blogger?
Dể xây dựng Progressive Web App, bạn cần phải thêm một số dịch vụ vào website của bạn. Các dịch vụ đó bao gồm: service workers, cho phép website hoạt động khi offline và thông báo đẩy. Bạn cũng có thể thêm thông báo Add-to-Home screen để nhắc người dùng thêm website hay ứng dụng của bạn vào màn hình điện thoại hoặc máy tính cá nhân.
Nghe thì có vẻ hơi khó hiểu và nó rất khó để thực hiện với Blogger, các bạn hãy chú ý và làm theo các bước hướng dẫn dưới đây thì các bạn có thể dễ dàng xây dựng được PWA cho Blogger.
Yêu cầu
Để xây dựng PWA cho Blogger, có một số yêu cầu cần thiết để thiết lập như sau:
- Bạn phải có một logo icon dạng
.png
với kích thước là 512x512. - 5 ảnh chụp màn hình Blog của bạn dạng
.png
. - Có một tài khoản GitHub.
- Có một tài khoản Cloudflare và tên miền của bạn phải được quản lý DNS ở đây.
Uploading Icons
- Logo icon với định dạng
.png
kích thước 512x512, các bạn đổi tên nó thànhandroid-icon-512x512.png
. - Vào trang favicon-generator.org và upload icon lên.
- Tại bộ favicon và icon vừa tạo và giải nén ra.
- Xoá bỏ một số file không cần thiết như
browserconfig.xml
vàmanifest.json
. - Vào Github và tạo một Repository, ví dụ như
icon-nldblog
và upload tất cả các file icon vào trong nhánhmain
.
*Bao gồm cả fileandroid-icon-512x512.png
.
Upload ảnh chụp màn hình
- Lưu 5 ảnh chụp màn hình Blog của bạn dưới dạng
.png
. - Đổi tên các file theo thứ tự như sau: scr1.png, scr2.png, scr3.png, scr4.png, scr5.png
- Tiếp tục upload 5 ảnh chụp màn hình vào nhánh main của GitHub Repository vừa tạo.
Tạo Workers trong Cloudflare
Chúng ta sẽ tạo 4 Workers trong Cloudflare, để dễ dàng hơn trong việc quản lý chúng ta lưu nó bằng tên worker và tên blog, như sau:
main-nldblog
manifest-nldblog
serviceworker-nldblog
offline-nldblog
Main Worker
- Đăng nhập vào tài khoản Cloudflare của bạn.
- Chọn vào Workers và chọn Manage Workers.
- Chọn vào Create a Service và đặt tên cho nó như sau
main-blogname
, ví dụ:main-nldblog
- Xoá bỏ đoạn Script có sẵn và thay thế bằng đoạn Script sau:
addEventListener("fetch", event => { event.respondWith(handleRequest(event)) }) //const BUCKET_NAME = "main" const BUCKET_URL = `https://cdn.statically.io/gh/ngylduy/icon-nldblog` 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 }) } }
- Thay thế
ngylduy
bằng GitHub username của bạn vàicon-nldblog
bằng tên của bạn Repository - Nhấn vào Save and Deploy.
Manifest.json
- Giống như cách tạo ở trên chúng ta tạo Service có tên như sau
manifest-blogname
. - Thay thế Script có sẵn bằng Script sau:
addEventListener("fetch", event => { const data = { name: "NLD BLOG", short_name: "NLD BLOG", description: "Install Now NLD BLOG App.", 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: "NLD BLOG", short_name: "NLD BLOG", description: "The Best Website where you can find Blogger Widgets, Tech News, Tech Reviews, Coding related Tutorials and many more.", url: "\/?utm_source=homescreen", icons: [ { src: "\/main\/android-icon-192x192.png", sizes: "192x192" } ] }, { name: "NLD BLOG", short_name: "NLD BLOG", description: "Explore NLD BLOG.", url: "\/search?utm_source=homescreen", icons: [ { src: "\/main\/android-icon-192x192.png", sizes: "192x192" } ] }, { name: "Giao diện Blogspot", short_name: "Giao diện Blogspot", description: "Giao diện miễn phí cho Blogspot.", url: "\/search\/label\/theme-blogspot?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" } }) ) })
- Thay thế những thông tin như tên và mã màu ở trên.
- Nhấn vào Save and Deploy.
ServiceWorker
- Như trên, tạo một Service tên như sau
serviceworker-blogname
. - Thay thế Script có sẵn bằng Script sau:
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)) })
- Sử dụng Workbox, chúng ta có thể lưu vào bộ đệm HTML, CSS, JS.
- Nhấn vào Save and Deploy.
Offline
- Vẫn như trên, chúng tạo một Service đặt tên là
offline-blogname
. - Thay thế Script có sẵn bằng Script sau:
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)) })
- Thay thế một số thông tin ở trên.
- Nhấn vào Save and Deploy.
Tạo Routes
- Bây giờ chúng ta quay lại Workers và nhấn vào Add Route.
- Nhập các thông tin như bảng sau:
Route | Service | Environment |
---|---|---|
www.nldblog.com/main/* | main-nldblog | production |
www.nldblog.com/manifest.json | manifest-nldblog | production |
www.nldblog.com/sw.js | serviceworker-nldblog | production |
www.nldblog.com/offline.html | offline-nldblog | production |
- * Nhớ thay thế url blog của bản và tên các service của bạn như đã đặt ở các bước trên.
Bây giờ bạn hãy thư vào các đường dẫn sau
www.nldblog.com/main/android-icon-512x512.png
www.nldblog.com/manifest.json
www.nldblog.com/sw.js
www.nldblog.com/offline.html
Nếu truy cập được và không bị chuyển vào link 404 là OK.
Chỉnh sửa trong Blogger
- Bây giờ truy cập vào Blogger và vào phần Giao diện.
- Chỉnh sửa HTML.
- Dán đoạn code dưới đây vào dưới thẻ
<head>
, một số theme là<head>
. Nhớ kiểm tra và xoá bỏ những đoạn code cũ tương tự nếu có.
<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'/>
- Dán tiếp đoạn JavaScript dưới đây vào trước thẻ
</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>
- Nhấp Lưu và xem kết quả.
Lời kết
Đây là toàn bộ cách xây dựng Progressive Web App cho Blogger, các bạn có thể xem demo bằng cách truy cập nldblog.com. Nếu có bất kì vấn đề và câu hỏi nào về bài viết hãy bình luận xuống bên dưới và mình sẽ giải đáp thắc mắc nhanh nhất có thể!
Your email address will not be published.
Enter Image URL / Code Snippets / Quotes / name tag, then click parse button accordingly that you have entered. then copy the parse result and paste it into the comment field.