When turned on automatically changes
the theme color on reload.
When you turn on mode
Combat your eye strain when reading.
When turned on automatically changes
the theme color every 5 sec.
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.

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.
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.
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.
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.
Để tạo Progressive Web App cho Blogger yêu cầu bạn phải có một tên miền tùy chỉnh và sử dụng dịch vụ của Cloudflare. Đối với tên miền mặc định .blogspot.com thì bạn không thể thiết lập được service worker.
Để 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:
.png với kích thước là 512x512..png..png kích thước 512x512, các bạn đổi tên nó thành android-icon-512x512.png.browserconfig.xml và manifest.json.icon-nldblog và upload tất cả các file icon vào trong nhánh main.
android-icon-512x512.png.
.png.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-blogname, ví dụ: main-nldblogaddEventListener("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 })
}
}
ngylduy bằng GitHub username của bạn và icon-nldblog bằng tên của bạn Repositorymanifest-blogname.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"
}
})
)
})
serviceworker-blogname.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-blogname.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))
})
| 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 |
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.
<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'/>
</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>
Đâ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ể!