Progressive Web Apps w 2025 - Kompletny przewodnik
Progressive Web Apps
mobileProgressive Web Apps w 2025 - Kompletny przewodnik
Progressive Web Apps (PWA) to aplikacje webowe, ktore lacza w sobie najlepsze cechy stron internetowych i aplikacji natywnych. Dzialaja w przegladarce, ale oferuja mozliwosci znane z aplikacji mobilnych: dzialanie offline, powiadomienia push, instalacje na ekranie glownym i plynne animacje. W 2025 roku PWA staly sie standardem w tworzeniu nowoczesnych aplikacji, ktore sa szybkie, niezawodne i angazujace.
Czym sa Progressive Web Apps?#
PWA to aplikacje webowe, ktore spelniaja okreslone kryteria techniczne i jakosciowe. Termin ten zostal wprowadzony przez Google w 2015 roku, ale od tamtego czasu technologia ta przeszla ogromna ewolucje. Kluczowe cechy PWA to:
- Progresywnosc - dzialaja dla kazdego uzytkownika niezaleznie od przegladarki
- Responsywnosc - dopasowuja sie do kazdego urzadzenia (desktop, tablet, telefon)
- Niezaleznosc od sieci - dzialaja offline lub przy slabym polaczeniu
- Podobienstwo do aplikacji - interakcje i nawigacja w stylu natywnym
- Aktualizacja - zawsze aktualne dzieki Service Workerom
- Bezpieczenstwo - serwowane przez HTTPS
- Odkrywalnosc - identyfikowane jako aplikacje dzieki manifestowi W3C
- Instalowalnosc - mozliwosc instalacji bez sklepu z aplikacjami
- Linkowalnosc - latwe udostepnianie przez URL
Service Workers - serce PWA#
Service Worker to skrypt JavaScript dzialajacy w tle, niezaleznie od strony. To wlasnie on odpowiada za dzialanie offline, synchronizacje w tle i powiadomienia push. Dziala jako proxy miedzy przegladarka a siecia.
Rejestracja Service Workera#
// app/register-sw.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
});
console.log('SW zarejestrowany:', registration.scope);
// Sprawdzanie aktualizacji
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'activated') {
console.log('Nowa wersja dostepna!');
}
});
});
} catch (error) {
console.error('Rejestracja SW nie powiodla sie:', error);
}
});
}
Cykl zycia Service Workera#
Service Worker przechodzi przez kilka stanow:
- Instalacja (
install) - pobieranie i cache'owanie zasobow - Oczekiwanie (
waiting) - czekanie na zamkniecie starych kart - Aktywacja (
activate) - czyszczenie starego cache - Przechwytywanie (
fetch) - obsluga zadan sieciowych
// sw.js
const CACHE_NAME = 'pwa-cache-v1';
const STATIC_ASSETS = [
'/',
'/offline.html',
'/css/styles.css',
'/js/app.js',
'/images/logo.webp',
];
// Instalacja - cache'owanie zasobow statycznych
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log('Cache otwarty');
return cache.addAll(STATIC_ASSETS);
})
);
self.skipWaiting();
});
// Aktywacja - czyszczenie starego cache
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
self.clients.claim();
});
Web App Manifest#
Manifest to plik JSON, ktory informuje przegladarke o aplikacji i okresla, jak powinna sie zachowywac po zainstalowaniu na urzadzeniu uzytkownika.
{
"name": "Moja Aplikacja PWA",
"short_name": "MojaPWA",
"description": "Przykladowa Progressive Web App",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0070f3",
"orientation": "portrait-primary",
"scope": "/",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"screenshots": [
{
"src": "/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/mobile.png",
"sizes": "375x812",
"type": "image/png",
"form_factor": "narrow"
}
],
"shortcuts": [
{
"name": "Nowy wpis",
"short_name": "Nowy",
"url": "/new",
"icons": [{ "src": "/icons/shortcut-new.png", "sizes": "96x96" }]
}
]
}
Dodanie manifestu do strony:
<head>
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#0070f3" />
<link rel="apple-touch-icon" href="/icons/icon-192x192.png" />
</head>
Strategie cache'owania - podejscie offline-first#
Wybor odpowiedniej strategii cache'owania jest kluczowy dla wydajnosci i doswiadczenia uzytkownika. Oto najwazniejsze strategie:
Cache First (Cache, Falling Back to Network)#
Najpierw sprawdza cache, a jesli zasob nie istnieje, pobiera z sieci. Idealna dla zasobow statycznych, ktore rzadko sie zmieniaja.
// Strategia Cache First
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request).then((response) => {
// Zapisz nowy zasob w cache
if (response.status === 200) {
const responseClone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
}
return response;
});
})
);
});
Network First (Network, Falling Back to Cache)#
Najpierw probuje pobrac z sieci, a jesli sie nie uda, zwraca wersje z cache. Idealna dla dynamicznych tresci, takich jak API.
// Strategia Network First
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
// Aktualizuj cache swiezymi danymi
const responseClone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
return caches.match(event.request).then((cachedResponse) => {
return cachedResponse || caches.match('/offline.html');
});
})
);
});
Stale While Revalidate#
Zwraca natychmiast wersje z cache (jesli dostepna), jednoczesnie pobierajac aktualizacje z sieci w tle. Idealna dla zasobow, ktore powinny byc szybko dostepne, ale tez aktualne.
// Strategia Stale While Revalidate
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return cachedResponse || fetchPromise;
});
})
);
});
Porownanie strategii#
| Strategia | Szybkosc | Swiezosc danych | Uzycie | |-----------|----------|-----------------|--------| | Cache First | Bardzo szybka | Moga byc nieaktualne | Fonty, obrazy, CSS | | Network First | Wolniejsza | Zawsze aktualne | API, dane dynamiczne | | Stale While Revalidate | Szybka | Aktualizowane w tle | Listy artykulow, profile |
Powiadomienia Push#
Powiadomienia push pozwalaja na angazowanie uzytkownikow nawet gdy aplikacja nie jest otwarta. Wymagaja Service Workera i API Push.
Subskrypcja powiadomien#
// Zapis na powiadomienia push
async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready;
// Sprawdz uprawnienia
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
console.log('Uzytkownik odmowil powiadomien');
return;
}
// Utworz subskrypcje
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
});
// Wyslij subskrypcje na serwer
await fetch('/api/push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription),
});
}
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}
Obsluga powiadomien w Service Workerze#
// sw.js - obsluga push
self.addEventListener('push', (event) => {
const data = event.data?.json() ?? {};
const options = {
body: data.body || 'Nowa wiadomosc!',
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
vibrate: [200, 100, 200],
data: { url: data.url || '/' },
actions: [
{ action: 'open', title: 'Otworz' },
{ action: 'dismiss', title: 'Odrzuc' },
],
};
event.waitUntil(
self.registration.showNotification(data.title || 'PWA', options)
);
});
// Obsluga klikniecia w powiadomienie
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.action === 'dismiss') return;
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});
Instalowalnosc PWA#
Jednym z kluczowych atutow PWA jest mozliwosc instalacji bez sklepu z aplikacjami. Przegladarka wyswietla przycisk instalacji, gdy aplikacja spelnia kryteria PWA.
// Obsluga zdarzenia beforeinstallprompt
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault();
deferredPrompt = event;
// Pokaz wlasny przycisk instalacji
const installBtn = document.getElementById('install-button');
installBtn.style.display = 'block';
installBtn.addEventListener('click', async () => {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`Uzytkownik ${outcome === 'accepted' ? 'zainstalowal' : 'odmowil'}`);
deferredPrompt = null;
installBtn.style.display = 'none';
});
});
// Wykrycie instalacji
window.addEventListener('appinstalled', () => {
console.log('Aplikacja zainstalowana!');
deferredPrompt = null;
});
Workbox - uproszczenie pracy z Service Workerami#
Workbox to zestaw bibliotek od Google, ktory upraszcza tworzenie Service Workerow. Zamiast pisac wszystko recznie, mozemy uzyc gotowych strategii.
// sw.js z Workbox
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import {
CacheFirst,
NetworkFirst,
StaleWhileRevalidate,
} from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
// Precaching - automatyczne cache'owanie plikow z buildu
precacheAndRoute(self.__WB_MANIFEST);
// Obrazy - Cache First z limitem 60 obrazow, max 30 dni
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images-cache',
plugins: [
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 dni
}),
new CacheableResponsePlugin({ statuses: [0, 200] }),
],
})
);
// API - Network First z fallback na cache
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-cache',
plugins: [
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 5 * 60, // 5 minut
}),
],
})
);
// CSS i JS - Stale While Revalidate
registerRoute(
({ request }) =>
request.destination === 'style' || request.destination === 'script',
new StaleWhileRevalidate({
cacheName: 'static-resources',
})
);
// Fonty Google - Cache First, dlugi czas zycia
registerRoute(
({ url }) => url.origin === 'https://fonts.googleapis.com' ||
url.origin === 'https://fonts.gstatic.com',
new CacheFirst({
cacheName: 'google-fonts',
plugins: [
new ExpirationPlugin({
maxEntries: 30,
maxAgeSeconds: 365 * 24 * 60 * 60, // 1 rok
}),
],
})
);
PWA w Next.js - konfiguracja krok po kroku#
Next.js swietnie nadaje sie do budowy PWA. Oto jak skonfigurowac PWA z uzyciem pakietu next-pwa:
Instalacja#
npm install next-pwa
Konfiguracja next.config.js#
// next.config.js
const withPWA = require('next-pwa')({
dest: 'public',
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === 'development',
runtimeCaching: [
{
urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts',
expiration: {
maxEntries: 4,
maxAgeSeconds: 365 * 24 * 60 * 60,
},
},
},
{
urlPattern: /^https:\/\/cdn\.example\.com\/.*/i,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'cdn-assets',
expiration: {
maxEntries: 32,
maxAgeSeconds: 24 * 60 * 60,
},
},
},
{
urlPattern: /\/api\/.*$/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-responses',
expiration: {
maxEntries: 16,
maxAgeSeconds: 60 * 60,
},
networkTimeoutSeconds: 10,
},
},
{
urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
handler: 'CacheFirst',
options: {
cacheName: 'static-images',
expiration: {
maxEntries: 64,
maxAgeSeconds: 30 * 24 * 60 * 60,
},
},
},
],
});
module.exports = withPWA({
// Pozostala konfiguracja Next.js
reactStrictMode: true,
});
Manifest w Next.js (App Router)#
// app/manifest.ts
import type { MetadataRoute } from 'next';
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Moja Aplikacja PWA',
short_name: 'MojaPWA',
description: 'Nowoczesna Progressive Web App',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#0070f3',
icons: [
{
src: '/icons/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/icons/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
};
}
Komponent instalacji PWA w React#
// components/InstallPWA.tsx
'use client';
import { useState, useEffect } from 'react';
interface BeforeInstallPromptEvent extends Event {
prompt: () => Promise<void>;
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
}
export function InstallPWA() {
const [deferredPrompt, setDeferredPrompt] =
useState<BeforeInstallPromptEvent | null>(null);
const [isInstalled, setIsInstalled] = useState(false);
useEffect(() => {
const handler = (e: Event) => {
e.preventDefault();
setDeferredPrompt(e as BeforeInstallPromptEvent);
};
const installedHandler = () => {
setIsInstalled(true);
setDeferredPrompt(null);
};
window.addEventListener('beforeinstallprompt', handler);
window.addEventListener('appinstalled', installedHandler);
// Sprawdz czy juz zainstalowana
if (window.matchMedia('(display-mode: standalone)').matches) {
setIsInstalled(true);
}
return () => {
window.removeEventListener('beforeinstallprompt', handler);
window.removeEventListener('appinstalled', installedHandler);
};
}, []);
const handleInstall = async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
setIsInstalled(true);
}
setDeferredPrompt(null);
};
if (isInstalled || !deferredPrompt) return null;
return (
<button
onClick={handleInstall}
className="fixed bottom-4 right-4 bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg hover:bg-blue-700 transition-colors z-50"
>
Zainstaluj aplikacje
</button>
);
}
Lighthouse PWA Audit#
Google Lighthouse to narzedzie do audytu jakosci stron, ktore zawiera dedykowany test PWA. Aby uzyskac pelna zgodnosc, Twoja aplikacja musi spelniac nastepujace kryteria:
- Szybkosc ladowania - strona laduje sie dostatecznie szybko w sieci 3G
- HTTPS - aplikacja serwowana jest przez bezpieczne polaczenie
- Service Worker - zarejestrowany i kontrolujacy strone
- Manifest - poprawny plik manifest z wymaganymi polami
- Ikony - ikony w odpowiednich rozmiarach (192x192 i 512x512)
- Kolor tematu - zdefiniowany meta tag theme-color
- Strona offline - aplikacja wyswietla cos nawet bez sieci
- Przekierowanie HTTP na HTTPS - automatyczne przekierowanie
# Audyt Lighthouse z CLI
npx lighthouse https://twoja-strona.pl --view --preset=desktop
# Tylko audyt PWA
npx lighthouse https://twoja-strona.pl --only-categories=pwa --output=json
Mozna rowniez uzyc zakladki Lighthouse w Chrome DevTools (F12 > Lighthouse > Analyze page load).
PWA vs aplikacje natywne#
| Cecha | PWA | Aplikacja natywna | |-------|-----|-------------------| | Instalacja | Z przegladarki, bez sklepu | Wymagany sklep (App Store / Google Play) | | Rozmiar | Kilka KB - MB | Dziesiatki - setki MB | | Aktualizacje | Automatyczne | Wymagana pobranie aktualizacji | | Dostep do API | Ograniczony, ale rosnacy | Pelny dostep do API urzadzenia | | Koszt tworzenia | Jedna baza kodu | Osobny kod na kazda platforme | | SEO | Pelne wsparcie | Brak (widocznosc w sklepie) | | Offline | Tak (Service Worker) | Tak (natywne przechowywanie) | | Push Notifications | Tak (web push) | Tak (natywne) | | Wydajnosc | Dobra, ale nizsza niz natywna | Najwyzsza | | Bluetooth/NFC | Ograniczone (Web Bluetooth API) | Pelne wsparcie |
Wsparcie przegladarek#
W 2025 roku wsparcie dla PWA jest bardzo szerokie:
- Chrome - pelne wsparcie na wszystkich platformach
- Edge - pelne wsparcie, oparte na Chromium
- Firefox - wsparcie Service Worker i manifest (ograniczona instalowalnosc)
- Safari/iOS - wsparcie od iOS 16.4, w tym push notifications i instalacja na ekranie glownym
- Samsung Internet - pelne wsparcie
- Opera - pelne wsparcie
Wazna zmiana w 2024/2025: Apple znacznie poprawilo wsparcie PWA na iOS, dodajac powiadomienia push w Safari i lepsze wsparcie dla instalowanych aplikacji webowych.
Przyklady PWA w praktyce#
Wiele znanych firm korzysta z PWA osiagajac imponujace wyniki:
- Twitter Lite - 65% wzrost stron na sesje, 75% wzrost tweetow, 20% spadek wspolczynnika odrzucen
- Pinterest - 60% wzrost zaangazowania uzytkownikow, 44% wzrost przychodow z reklam
- Starbucks - PWA 99.84% mniejsza niz natywna aplikacja iOS, podwojenie liczby zamowien online
- Uber - aplikacja laduje sie w 3 sekundy nawet na sieci 2G
- Trivago - 150% wzrost zaangazowania po wdrozeniu PWA
Zaawansowane funkcje PWA w 2025#
Background Sync#
Synchronizacja danych w tle, gdy uzytkownik odzyska polaczenie:
// Rejestracja synchronizacji
async function saveData(data) {
try {
await fetch('/api/data', {
method: 'POST',
body: JSON.stringify(data),
});
} catch {
// Zapisz dane lokalnie i zarejestruj sync
await saveToIndexedDB('pending-sync', data);
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-data');
}
}
// sw.js - obsluga synchronizacji
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-data') {
event.waitUntil(syncPendingData());
}
});
async function syncPendingData() {
const pendingData = await getFromIndexedDB('pending-sync');
for (const data of pendingData) {
await fetch('/api/data', {
method: 'POST',
body: JSON.stringify(data),
});
await removeFromIndexedDB('pending-sync', data.id);
}
}
Periodic Background Sync#
Okresowa synchronizacja danych:
// Rejestracja okresowej synchronizacji
const registration = await navigator.serviceWorker.ready;
const status = await navigator.permissions.query({
name: 'periodic-background-sync',
});
if (status.state === 'granted') {
await registration.periodicSync.register('update-content', {
minInterval: 24 * 60 * 60 * 1000, // co 24 godziny
});
}
// sw.js
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'update-content') {
event.waitUntil(updateContent());
}
});
Web Share API#
Natywne udostepnianie tresci:
async function shareContent() {
if (navigator.share) {
try {
await navigator.share({
title: 'Moja aplikacja PWA',
text: 'Sprawdz ta niesamowita aplikacje!',
url: window.location.href,
});
} catch (err) {
console.log('Udostepnianie anulowane');
}
}
}
Najlepsze praktyki PWA#
- Zacznij od strategii offline - projektuj aplikacje z myslą o dzialaniu offline od poczatku
- Optymalizuj rozmiar cache - nie cache'uj wszystkiego, ustalaj limity
- Aktualizuj Service Worker inteligentnie - informuj uzytkownikow o nowych wersjach
- Uzywaj HTTPS - wymagane dla Service Workerow
- Testuj offline - Chrome DevTools > Application > Service Workers > Offline
- Mierz wydajnosc - regularne audyty Lighthouse
- Laduj progresywnie - pokazuj szkielet strony (skeleton), a potem laduj dane
- Uzywaj App Shell Pattern - cache'uj minimalny interfejs i laduj tresc dynamicznie
Podsumowanie#
Progressive Web Apps w 2025 roku to dojrzala technologia, ktora oferuje najlepsze z obu swiatow: zasiegu stron internetowych i doswiadczenia aplikacji natywnych. Dzieki Service Workerom, Web App Manifest i nowoczesnym API przegladarkowym, PWA pozwalaja tworzyc szybkie, niezawodne i angazujace aplikacje, ktore dzialaja na kazdym urzadzeniu.
Kluczem do sukcesu jest wybor odpowiednich strategii cache'owania, dobrze zaprojektowana architektura offline-first i regularne testowanie z Lighthouse. Narzedzia takie jak Workbox i framework-owe integracje (np. next-pwa) znacznie upraszczaja proces tworzenia PWA.
Potrzebujesz pomocy przy tworzeniu Progressive Web App lub modernizacji istniejącej aplikacji? MDS Software Solutions Group specjalizuje się w budowie nowoczesnych aplikacji webowych z pelnym wsparciem PWA. Skontaktuj się z nami, aby omówić Twój projekt i dowiedzieć się, jak PWA moze poprawic zaangazowanie Twoich uzytkownikow.
Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.