EDNEI
EDNEITRABACH
>Início>Projetos>Experiência>Sobre>Blog
GitHubLinkedInInstagram
status: disponível
>Início>Projetos>Experiência>Sobre>Blog
status: disponível

Conecte-se

Vamos construir algo junto

Sempre interessado em colaborações, problemas interessantes e conversas sobre código, design e tudo mais.

enviar mensagem→

Me encontre também

GitHub
@EdneiTrabach
LinkedIn
/in/edneitrabach
Instagram
@edneitrabach
WhatsApp
Conversar
Desenvolvido com& código

© 2026 EdneiTrabach — All experiments reserved

back to blog
frontend

Construindo PWAs com Vue.js 3: Experiência Offline Real

Guia prático para criar Progressive Web Apps com Vue.js 3, Vite e Workbox. Transformando aplicações web em experiências nativas.

ET

Ednei Trabach

Desenvolvedor Full Stack

15 de dezembro de 202412 min read
#pwa#vuejs#vite#offline

O que é uma PWA?

Progressive Web Apps (PWAs) combinam o melhor de web e mobile:

  • Instaláveis como apps nativos
  • Funcionam offline
  • Notificações push
  • Performance otimizada
  • Responsivas por design

Setup Inicial

Criar projeto Vite + Vue

npm create vite@latest my-pwa -- --template vue-ts
cd my-pwa
npm install

Instalar dependências PWA

npm install -D vite-plugin-pwa workbox-precaching
npm install register-service-worker

Configuração do Vite

vite.config.ts

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]
              }
            }
          }
        ]
      }
    })
  ]
})

Registro do Service Worker

main.ts

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')

Estratégias de Cache

Cache First (Assets estáticos)

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
        }
      }
    }
  ]
}

Network First (API)

{
  urlPattern: /^https://api\.example\.com\/.*$/,
  handler: 'NetworkFirst',
  options: {
    cacheName: 'api-cache',
    networkTimeoutSeconds: 10,
    expiration: {
      maxEntries: 50,
      maxAgeSeconds: 5 * 60 // 5 minutos
    }
  }
}

Stale While Revalidate (Conteúdo dinâmico)

{
  urlPattern: /^https://cdn\.example\.com\/.*$/,
  handler: 'StaleWhileRevalidate',
  options: {
    cacheName: 'cdn-cache',
    expiration: {
      maxEntries: 100,
      maxAgeSeconds: 60 * 60 // 1 hora
    }
  }
}

Componente de Status Offline

OfflineStatus.vue

<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>

API com Fallback Offline

useApi.ts

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 }
}

Notificações Push

Solicitar permissão

async function requestNotificationPermission() {
  if ('Notification' in window) {
    const permission = await Notification.requestPermission()
    if (permission === 'granted') {
      new Notification('Notificações ativadas!')
    }
  }
}

Enviar notificação

function sendNotification(title: string, body: string) {
  if ('Notification' in window && Notification.permission === 'granted') {
    new Notification(title, { body })
  }
}

Testando Offline

Chrome DevTools

  1. Abra DevTools (F12)
  2. Vá para a aba "Network"
  3. Marque "Offline"
  4. Recarregue a página

Lighthouse

npm install -g lighthouse
lighthouse http://localhost:5173 --view

Checklist PWA

  • [ ] Manifest web app configurado
  • [ ] Service worker registrado
  • [ ] Estratégias de cache definidas
  • [ ] Ícones em múltiplos tamanhos
  • [ ] Status offline implementado
  • [ ] HTTPS configurado (obrigatório)
  • [ ] Meta tags PWA adicionadas
  • [ ] Testado em múltiplos dispositivos
  • [ ] Performance otimizada (Lighthouse > 90)

Conclusão

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.

share
share:
[RELATED_POSTS]

Continue Reading

frontend

Vue.js: Composition API vs Options API - Qual Escolher?

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.

15 de janeiro de 2025•8 min read
frontend

TypeScript Strict Mode: Padrões para Máxima Produtividade

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.

20 de dezembro de 2024•15 min read