Back to home
Daily Chronicle Logo

Daily Chronicle

Web Development
December 10, 2025

Build Native-Like Apps from Your Website with PWA

Ever wished your website could work like a native app? Install on home screens, work offline, send notifications? That's exactly what Progressive Web Apps (PWAs) do—they bridge the gap between traditional websites and native applications without requiring app store approval or separate codebases.

Let's build a practical timer app to see how PWAs work in action.

What Makes a PWA Different?

A PWA is essentially a website enhanced with three key features:

Installability - Users can add it to their home screen like a native app Offline functionality - Works without internet connection Push notifications - Engage users even when the app isn't open

These capabilities come from two core technologies: the Web App Manifest and Service Workers.

Building Our Timer App

We'll create a simple countdown timer that demonstrates PWA fundamentals. When the timer completes, it sends a notification—perfect for showing how PWAs can behave like native apps.

The Web App Manifest

Create a manifest.json file that tells browsers your website is installable (MDN Docs):

{
  "name": "Timer PWA",
  "short_name": "Timer",
  "description": "A simple countdown timer",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#6200ea",
  "icons": [
    {
      "src": "/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Link it in your HTML:

<link rel="manifest" href="/manifest.json">

The "display": "standalone" makes your app open without browser UI, just like a native app. Learn more about display modes.

Basic Timer HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Timer PWA</title>
  <link rel="manifest" href="/manifest.json">
</head>
<body>
  <div id="app">
    <h1>Timer</h1>
    <input type="number" id="minutes" value="1" min="1">
    <button id="start">Start</button>
    <div id="display">00:00</div>
  </div>
  <script src="/app.js"></script>
</body>
</html>

Timer Logic with Notifications

// app.js
let timerInterval;

// Request notification permission
if ('Notification' in window) {
  Notification.requestPermission();
}

document.getElementById('start').addEventListener('click', () => {
  const minutes = parseInt(document.getElementById('minutes').value);
  let secondsLeft = minutes * 60;
  
  timerInterval = setInterval(() => {
    secondsLeft--;
    
    const mins = Math.floor(secondsLeft / 60);
    const secs = secondsLeft % 60;
    document.getElementById('display').textContent = 
      `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    
    if (secondsLeft <= 0) {
      clearInterval(timerInterval);
      showNotification();
    }
  }, 1000);
});

function showNotification() {
  if (Notification.permission === 'granted') {
    new Notification('Timer Complete!', {
      body: 'Your countdown has finished.',
      icon: '/icon-192.png'
    });
  }
}

// Register Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(() => console.log('Service Worker registered'))
    .catch(err => console.log('Service Worker registration failed:', err));
}

Learn more about the Notifications API and Service Worker registration.

The Service Worker

Create sw.js for offline functionality (Service Worker API docs):

const CACHE_NAME = 'timer-v1';
const urlsToCache = [
  '/',
  '/app.js',
  '/manifest.json',
  '/icon-192.png'
];

// Install - cache resources
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

// Fetch - serve from cache, fallback to network
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

This implements a cache-first strategy. Explore different caching strategies for various use cases.

Testing Your PWA

  1. Serve your app over HTTPS (required for Service Workers)
  2. Open in Chrome/Edge and look for the install icon in the address bar
  3. Click "Install" and your app appears like a native application
  4. Try turning off your internet—the app still works!
  5. Start a timer and see the notification when it completes

Use Lighthouse in Chrome DevTools to audit your PWA and get improvement suggestions.

When Should You Use PWAs?

PWAs are ideal when you want:

  • Cross-platform reach without maintaining separate codebases
  • Instant updates (no app store review process)
  • Lower development costs compared to native apps
  • Web-based distribution (SEO, shareable links)

They're not ideal for apps requiring deep device integration (like fitness trackers needing constant sensor access) or complex 3D graphics.

Browser Support

Modern browsers (Chrome, Edge, Safari, Firefox) support PWA features, though Safari has some limitations with push notifications on iOS. Always check Can I use for current compatibility.

Next Steps

You've built your first PWA! To go further:

Progressive Web Apps democratize app development—no app stores, no gatekeepers, just the open web enhanced with native capabilities.

Check out the complete PWA documentation on web.dev to dive deeper.

Related Posts

© 2025 Daily Chronicle