Guia prático para criar Progressive Web Apps com Vue.js 3, Vite e Workbox. Transformando aplicações web em experiências nativas.
Ednei Trabach
Desenvolvedor Full Stack
Progressive Web Apps (PWAs) combinam o melhor de web e mobile:
npm create vite@latest my-pwa -- --template vue-ts
cd my-pwa
npm install
npm install -D vite-plugin-pwa workbox-precaching
npm install register-service-worker
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
vue(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.svg', 'robots.txt'],
manifest: {
name: 'Minha PWA',
short_name: 'Minha PWA',
description: 'Uma PWA moderna com Vue.js 3',
theme_color: '#3b82f6',
background_color: '#ffffff',
display: 'standalone',
icons: [
{
src: '/favicon.svg',
sizes: '192x192',
type: 'image/svg+xml'
},
{
src: '/favicon.svg',
sizes: '512x512',
type: 'image/svg+xml'
}
]
},
workbox: {
globPatterns: ['**/*.{js,css,html,svg,png,woff2}'],
runtimeCaching: [
{
urlPattern: /^https://api\.example\.com\/.*$/,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 // 24 horas
},
cacheableResponse: {
statuses: [0, 200]
}
}
}
]
}
})
]
})
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'
import { registerSW } from 'virtual:pwa-register'
const updateSW = registerSW({
onNeedRefresh() {
if (confirm('Nova versão disponível. Recarregar?')) {
updateSW(true)
}
},
onOfflineReady() {
console.log('PWA pronta para uso offline')
}
})
createApp(App).mount('#app')
workbox: {
runtimeCaching: [
{
urlPattern: /.(?:png|jpg|jpeg|svg|gif|webp)$/,
handler: 'CacheFirst',
options: {
cacheName: 'image-cache',
expiration: {
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 dias
}
}
}
]
}
{
urlPattern: /^https://api\.example\.com\/.*$/,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
networkTimeoutSeconds: 10,
expiration: {
maxEntries: 50,
maxAgeSeconds: 5 * 60 // 5 minutos
}
}
}
{
urlPattern: /^https://cdn\.example\.com\/.*$/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'cdn-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 // 1 hora
}
}
}
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const isOnline = ref(navigator.onLine)
const updateOnlineStatus = () => {
isOnline.value = navigator.onLine
}
onMounted(() => {
window.addEventListener('online', updateOnlineStatus)
window.addEventListener('offline', updateOnlineStatus)
})
onUnmounted(() => {
window.removeEventListener('online', updateOnlineStatus)
window.removeEventListener('offline', updateOnlineStatus)
})
</script>
<template>
<div
v-if="!isOnline"
class="fixed bottom-4 right-4 bg-yellow-500 text-white px-4 py-2 rounded-lg shadow-lg"
>
⚠️ Você está offline
</div>
</template>
import { ref } from 'vue'
export function useApi<T>(key: string) {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
async function fetch() {
loading.value = true
error.value = null
try {
const response = await fetch(`/api/${key}`)
if (!response.ok) throw new Error('Falha na requisição')
data.value = await response.json()
// Salvar no localStorage para fallback
localStorage.setItem(`cache_${key}`, JSON.stringify(data.value))
} catch (err) {
error.value = err as Error
// Tentar carregar do cache
const cached = localStorage.getItem(`cache_${key}`)
if (cached) {
data.value = JSON.parse(cached)
}
} finally {
loading.value = false
}
}
return { data, loading, error, fetch }
}
async function requestNotificationPermission() {
if ('Notification' in window) {
const permission = await Notification.requestPermission()
if (permission === 'granted') {
new Notification('Notificações ativadas!')
}
}
}
function sendNotification(title: string, body: string) {
if ('Notification' in window && Notification.permission === 'granted') {
new Notification(title, { body })
}
}
npm install -g lighthouse
lighthouse http://localhost:5173 --view
PWAs transformam aplicações web em experiências de primeira classe. No projeto Estância DM (PWA para hóspedes), a experiência offline foi crucial para hóspedes com conexão instável.
A curva de aprendizado é moderada, mas os benefícios em engajamento e experiência do usuário valem o investimento.
Uma análise prática comparando Composition API e Options API no Vue.js 3. Quando usar cada abordagem e como elas impactam a organização do código.
Como configurar e usar TypeScript strict mode de forma eficaz. Padrões práticos que melhoram a qualidade do código sem sacrificar velocidade de desenvolvimento.