You get a bonus - 1 coin for daily activity. Now you have 1 coin

Building PWA web applications

Lecture



What Progressive Web Apps (PWA) are

Progressive Web Apps (PWA) are web applications that use standard web technologies (HTML, CSS, JavaScript) yet behave like native applications on devices. They combine the best of websites and mobile apps.

Key characteristics

  • Installation on the device The user can "install" a site as an app: it gets its own icon on the desktop or in the applications menu.

  • Offline operation With the help of Service Workers, PWAs can cache data and work even without an internet connection.

  • Native interface They launch in a separate window with no browser chrome, creating the feel of a real application.

  • Cross-platform support A single codebase works on different devices and operating systems (Windows, Android, iOS, macOS).

  • Automatic updates They update the same way websites do — with no need to download new versions from a store.

Advantages

  • For business: cheaper than developing separate apps for iOS and Android.

  • For users: convenient access, less space taken up on the device, the ability to work offline.

  • For developers: a single codebase, easy maintenance and scaling.

Key elements of a PWA

Element Purpose
manifest.webmanifest A JSON file with settings (name, icons, theme colour, start URL).
Service Worker A script for caching, offline mode and push notifications.
HTTPS A mandatory requirement for security and for Service Workers to work.
Responsive Design Adapting the interface to different devices.

Comparing a PWA and kiosk mode

Criterion Progressive Web App (PWA) Kiosk mode
Purpose Make a site resemble a native app that can be installed on a device Restrict the device to working with a single app/site only
Manifest file manifest.webmanifest describes the name, icons, colours and start URL Usually not used; configuration is done through the OS or third-party applications
Installation Installed voluntarily by the user (desktop icon, applications menu) An administrator enables the mode, and the device launches only the selected application
Flexibility Can be launched either as an ordinary site or as an app A hard restriction: the user cannot go beyond the selected application
Offline mode Supported via a Service Worker Depends on the application; kiosk mode itself adds no offline features
Security Requires HTTPS; push notifications and caching can be added The main goal is physical and logical protection: preventing access to other device functions
Use cases Handy for sites that want to feel "like an app" (shops, services, news portals) Used in public terminals, interactive kiosks and self-service points

The key difference

  • A PWA is about convenience and universality: a site turns into an app.

  • Kiosk mode is about control and restriction: the device runs only one application, so that the user cannot do anything else.

The manifest.webmanifest file is used in Progressive Web Apps (PWA).

What this means

  • This is the Web App Manifest — a JSON file that describes the parameters of a web application.

  • It tells the browser how the site should behave when installed on a device: name, icons, theme colour, screen orientation and so on.

  • Having such a file is a mandatory requirement for a site to be installable as an app on a smartphone or computer.

Key points

  • Type of applications: Progressive Web Apps (PWA).

  • Format: JSON with the MIME type application/manifest+json.

  • Purpose: turns a site into an "almost native" application — with an icon, full-screen mode and the ability to work offline.

Why the browser automatically created an "application" out of a web page

Modern browsers (especially Chrome, Edge and others) can offer to install a site as a PWA — a Progressive Web App.
If the site meets certain conditions, the browser automatically shows an "Install app" button or even installs it by itself (on some systems).

💡 The conditions by which the browser decides that a site is an application:

  1. The presence of manifest.json — a special file describing the application:

    { "name": "My App", "start_url": "/", "display": "standalone", "icons": [...] } 
  2. HTTPS support — the site must work over a secure protocol.

  3. Service Worker — a script that manages the cache and network requests (for offline mode, for example).

  4. The site behaves like an application (for example, with no visible address bar, with an icon and its own shell).

As a result, the browser creates an isolated application (WebApp) out of the page, launched separately from tabs.

How to check that AJAX requests come from the app rather than from an ordinary browser tab

There are several ways to tell these cases apart — from simple to advanced.

1. Check the request headers (User-Agent, Referer)

  • A PWA (an installed application) usually has no Referer (or has it as null).

  • An ordinary tab will have a Referer with the page URL.

On the server (a PHP example):

if (empty($_SERVER['HTTP_REFERER'])) { echo "Probably PWA or standalone app"; } else { echo "Browser tab: " . $_SERVER['HTTP_REFERER']; } 

2. Check the context on the client side

In JS you can determine whether the site is open as a PWA:

if (window.matchMedia('(display-mode: standalone)').matches) { console.log('Running as installed PWA'); } else { console.log('Running in browser tab'); } 

You can pass this along in an AJAX request:

fetch('/api/check', { method: 'POST', headers: { 'X-From-PWA': window.matchMedia('(display-mode: standalone)').matches ? 'yes' : 'no' } }); 

3. Use a different origin or scope in manifest.json

A PWA can have its own scope, for example /app/, in which case all AJAX requests from it will carry the corresponding Origin and Referer.

4. Check for the presence of a Service Worker

If requests are handled through a Service Worker, you can look at:

  • DevTools → Application → Service Workers

  • The console or the network panel: if a request comes "from ServiceWorker", it is a PWA.

In brief:

Indicator Ordinary tab Installed PWA
Referer Present Often null
display-mode browser standalone
Service Worker May be absent Almost always present
Display A tab with an address bar A separate window
User-Agent An ordinary browser The same, but with no Referer

If a user has such an app installed, are all the scripts cached on their machine and not updated when the scripts on the site change?

One of the key features of a PWA (Progressive Web App) is caching.

Indeed, if a user has installed the site as an app, the browser may use the old version of the scripts even after the site has been updated.
Let's go through in detail why this happens and how to control it.

Why scripts are not updated automatically

When a site becomes a PWA, it gains a Service Worker — a background script that:

  • intercepts all network requests,

  • can cache HTML, JS, CSS, images and API responses,

  • returns cached data instead of loading from the server.

So if your site has been updated but the user already has a Service Worker with cached versions —
they will keep seeing the old files (an old main.js, for example) until the Service Worker updates.

How the update actually happens

A Service Worker does not update instantly, but according to certain rules:

  1. The browser checks whether service-worker.js has changed (by content or ETag).

  2. If the file is different, a "new version" of the Service Worker is created.

  3. The new version is installed, but is not activated until the user:

    • closes all tabs/windows of the app,

    • or restarts the PWA.

  4. Only then is the cache updated.

This means updates can be delayed by several hours or even days.

How to make a PWA update its scripts properly

Here is how it is done in practice:

1. File versioning

Add a version number to the file names:


 

or add a query parameter:


 

The browser treats this as a new URL, so the cache is not used.

2. Cache control through the Service Worker

In service-worker.js you can set a strategy:

self.addEventListener('fetch', event => { event.respondWith( caches.open('my-cache-v3').then(cache => { return fetch(event.request).then(response => { cache.put(event.request, response.clone()); return response; }).catch(() => cache.match(event.request)); }) ); }); 

Note my-cache-v3 — when you change the version (v3 → v4), the old cache will be dropped.

3. Forcing a Service Worker update

In service-worker.js you need to add:

self.skipWaiting(); self.clients.claim(); 

This makes the new SW activate immediately, without waiting for the old one to be closed.

But be careful — if the user is doing something at the moment of the update,
a conflict between old and new caches may occur (JS errors, for example).

4. Checking for freshness on the client

You can add an automatic check:

navigator.serviceWorker.getRegistration().then(reg => { if (reg) { reg.update(); // force a check for a new version } }); 

In brief:

Behaviour Cause Solution
Scripts are not updated The Service Worker cache Versioning or update()
The site loads offline The PWA caches all resources Configure the cache strategy
The user sees an old version The SW has not activated skipWaiting(), clients.claim()

If the app does not use a Service Worker, caching behaves differently —
and it really can be confusing, especially with PWAs created "automatically" by the browser.

Let's go through it in detail:

What happens if there is no Service Worker

When you install a site as an app (a PWA, or simply a "shortcut" via Chrome → "Add to desktop"),
the browser does the following:

  1. Creates a separate container (WebApp) with a minimal shell.

  2. Opens the site in display-mode: standalone — with no address bar.

  3. Continues to use the ordinary browser cache — that is:

    • the HTTP cache (Cache-Control, ETag, Last-Modified);

    • caching of DNS and resources (JS, CSS, images);

    • local data (localStorage, IndexedDB, cookies).

In other words, in this case updates depend not on a Service Worker but on the standard HTTP caching rules.

Why it may still seem that scripts are "not updating"

There are several reasons:

1. The HTTP headers allow caching

Check that the server is not sending too "long" a cache lifetime.
For example, this is a bad example:

Cache-Control: public, max-age=31536000 

This makes the browser keep JS files for a whole year.

Better:

Cache-Control: no-cache 

or:

Cache-Control: max-age=0, must-revalidate 

2. The file has the same URL — the browser does not re-request it

The browser may assume that main.js has not changed,
and take it from the local cache (especially in standalone mode).

The solution is to add versioning:


 

3. A PWA (or standalone app) keeps the cache longer than a tab

Chrome and Edge often cache resources more aggressively in app mode,
so that startup is instant.
In this case updates are only picked up after a forced check (Ctrl+F5)
or when the headers change.

4. Offline Cache / AppData (Windows)

If the user installed the app through Chrome,
the files may be stored in a system folder such as:

%LOCALAPPDATA%\Google\Chrome\User Data\Default\Web Applications\ 

Sometimes Chrome takes static resources (old JS versions) from there.
Deleting the app or its data solves the problem.

How to check where the scripts are being loaded from

  1. Open DevTools → Network.

  2. Reload the page inside the app (Ctrl+R).

  3. Look at the Size or Status column:

    • from disk cache / from memory cache → the old cache.

    • 200 OK → the file was actually downloaded from the server.

You can also clear the cache for a specific domain:

chrome://settings/clearBrowserData 

or for a specific site:

chrome://inspect/#apps 

Conclusions on caching

Situation What is cached How to update
Without a Service Worker The ordinary browser HTTP cache Change the headers or the URL
With a Service Worker A programmatic cache in JS Update the SW and the cache version
An installed PWA May keep it longer Clearing the cache or a version in the URL

If a user has the app installed but you remove the tag with manifest.json from the site, will the app keep launching?

This is an important nuance of how installed PWAs (Progressive Web Apps) behave.

The short answer

Yes, the app will keep launching,
even if you remove the tag from the site.

But:
The browser will no longer be able to update the app's information (icon, name, theme colour, behaviour and so on).

A detailed explanation

When a user installs a PWA (or "adds a site to the desktop"), the browser:

  1. Loads manifest.json;

  2. Saves its contents in its own internal storage (in the user's profile);

  3. Creates a system "shell" for launching the app (.desktop, a shortcut, an icon, and so on);

  4. No longer depends on manifest.json being present on the server at startup.

That is, after installation everything is already local:

  • Icons — copied into the system;

  • Name — saved;

  • Launch URL (start_url) — recorded;

  • Display (display-mode: standalone) — fixed.

What will happen after manifest.json is deleted

Behaviour Result
The app is already installed ✅ Will keep working
The user opens the site in the browser ❌ The "Install" button will disappear
Reinstallation ❌ Impossible — there is no manifest.json
Updating the name, icon, etc. ❌ Will not happen; the browser cannot check
Removing the site and installing it again ⚠️ Impossible without a manifest

How to check this in practice

  1. Install the site as an app (via Chrome → ⋮ → "Install app").

  2. After installation, delete manifest.json or the tag

  3. Restart the app — it will open, because the data is stored locally.

  4. Open the same site in a browser tab — the install button will no longer be there.

Additional nuances

  • If manifest.json previously had a start_url, the app will always open at exactly that URL — even if the site now has different code or structure.

  • If the site is unavailable, the app will show a connection error (no Service Worker means no offline mode).

  • Chrome, Edge, Opera and other Chromium browsers all behave the same way.
    Firefox is slightly different: its PWA support is weak overall, and it may fail to launch without a manifest after the profile is reloaded.

Summary

Action App behaviour
Remove Works, but without updates
Delete manifest.json itself Works, but cannot be reinstalled
Change manifest.json The changes will not be picked up until reinstallation
Clear the site data (AppData / Storage) The app will stop working

How can you determine programmatically (via JS) that the user is already using the installed app (even if manifest.json is no longer on the server)?

Here are several reliable ways in JavaScript to determine that the user has opened the installed app (a PWA),
even if manifest.json no longer exists on the site.

1. Checking the display mode (display-mode)

The most reliable and simplest way is via window.matchMedia:


if (window.matchMedia('(display-mode: standalone)').matches) { 
    console.log(' Launched as an installed app (PWA)'); 
} else {
    console.log(' Opened in a browser tab');
 } 

This works even without manifest.json,
because the browser remembers the display-mode when the app is installed.

2. Checking navigator.standalone (for iOS/Safari)

Safari on iOS uses its own flag:

if (window.navigator.standalone) { 
    console.log('Installed app on iOS'); 
} 

This flag exists only in Safari (not in Chrome/Edge).

3. A combined universal option

To support all platforms:

function isRunningAsApp() {
    return ( window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true ); 
} 

if (isRunningAsApp()) { 
console.log('The app is running in standalone mode');
 } else { 
console.log(' An ordinary browser tab');
 } 

4. Checking via document.referrer (additionally)

Installed apps often have no Referer,
unlike ordinary tabs:

if (!document.referrer) {
 console.log('Probably launched as a standalone app');
 } else { console.log('Opened from another page or tab'); 
} 

You can use this as an additional signal.

5. Checking the load source (via window.location)

Sometimes an installed app may have a special start_url
(for example, /app/ or /home.html) that differs from the ordinary site.

You can do it like this:

if (window.location.pathname.startsWith('/app')) { console.log(' The app was launched from an app path'); } 

This is handy if you set a start_url in manifest.json at installation time.

Bonus: send the fact that it is running as a PWA to the server

You can add this to your AJAX code:

fetch('/api/ping', { method: 'POST', headers: { 'X-From-PWA': isRunningAsApp() ? 'true' : 'false' } }); 

Then on the server (in PHP, for example) you will be able to tell them apart:

if ($_SERVER['HTTP_X_FROM_PWA'] === 'true') { // code for the app } 

Check it in DevTools

If you want to make sure the condition works:

  • Open the (installed) app.

  • Open DevTools → Console.

  • Paste the code from point 1.

  • It should print "Launched as an installed app (PWA)".

Let's take a closer look at how convenient start_url in manifest.json is.

Let's go through in detail what start_url in manifest.json is,
and why it matters — especially if you are working with an installed app (a PWA).

What start_url is

start_url is the path (URL) from which the installed web application (PWA) is launched.

It is set in the manifest.json file, for example:

{ "name": "My Cool App", "start_url": "/app/home.html", "display": "standalone", "icons": [ { "src": "/icons/icon192.png", "sizes": "192x192", "type": "image/png" } ] } 

What start_url does

When the user installs the site as an app, the browser:

  • saves this value (/app/home.html);

  • and afterwards always opens the app at exactly this URL,
    even if the user clicks the shortcut rather than a link.

In other words, it is the "entry point" of your application.

Example

Suppose your site is:

https://example.com/ 

and in manifest.json you specified:

"start_url": "/dashboard" 

Now:

  • when the app is opened from the menu/shortcut, the browser will launch:
    https://example.com/dashboard

  • but if the user simply opens the site manually, they will see https://example.com/

This means it lets you separate the behaviour of:

  • the site for ordinary visitors;

  • and the app for users who installed it.

Why this is convenient

Because you can:

  • point the app at a different interface (/app/, /panel/, /offline.html);

  • use different versions of the site;

  • or serve a different JS file / a different layout,
    specifically for users who installed the app.

A usage example

You can use start_url together with a JS check:

if (window.matchMedia('(display-mode: standalone)').matches) { console.log('App started from', window.location.href); } 

And if window.location.pathname matches the value from start_url,
then the user launched the installed app rather than simply opening the site.

In brief:

Property Purpose
start_url The URL from which the app launches after installation
Where it works In Chrome, Edge, Opera, Android WebView
What it does Defines the "entry point" for the PWA
Can be used for Separating site and app logic

What if the start_url parameter is not specified in manifest.json?

Let's work out what happens if the start_url parameter is not specified in manifest.json.

The short answer

If manifest.json has no "start_url" field,
then by default the browser uses the URL of the page
from which the user installed the app.

A detailed explanation

When the user clicks "Install app" (or "Add to desktop"),
the browser looks at:

"Which page am I being installed from right now?"

If manifest.json has no start_url, it simply saves the current address
and makes it the launch point.

Example 1 — without start_url

manifest.json:

{ "name": "MyApp", "display": "standalone" } 

The user installed the site from:

https://example.com/index.html 

After installation the app will always launch from

https://example.com/index.html 

Example 2 — with a subpage

If the user installed the app from another page, for example:

https://example.com/app/dashboard 

Then when the app is launched it will always open at exactly this URL,
even if you later change the site's structure.

Possible problems

  1. Unpredictable behaviour
    If different users install the app from different pages,
    each of them will have their own "entry point".
    One will land on /, another on /shop/, a third on /app/dashboard.

  2. Problems when updating
    If you change the routes or delete the page
    from which the app was installed,
    the user may get a 404 error on launch.

  3. You cannot manage the app's start point centrally,
    because the URL depends on where the user was at the time of installation.

Recommendation

It is better to always specify start_url explicitly, for example:

{ "name": "MyApp", "short_name": "App", "start_url": "/app/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#2196f3", "icons": [ { "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" } ] } 

That way you control where the app starts for all users,
and you can prepare the interface specifically for display-mode: standalone.

In summary:

Scenario What will happen
start_url specified The app always launches from this address
start_url not specified The app launches from the address where it was installed
The page has been deleted ❌ The app stops working (404 error)
Recommendation ✅ Always specify start_url explicitly

is there a difference between

and

the format and file extension of a PWA manifest.

The short answer

There is no functional difference between:


 

and


 

Both options are completely equivalent for the browser.
The main thing is that:

  • the file has the correct MIME type (application/manifest+json);

  • and contains valid JSON.

In detail

1. What matters to the browser

The browser does not care what the file is called —
manifest.json, manifest.webmanifest, or even myapp.manifest.

It goes by:

  • rel="manifest" — this is the signal that it is a PWA manifest;

  • the correct MIME type (HTTP header);

  • correct JSON syntax.

2. Why .webmanifest appeared

The .webmanifest extension is the new recommended standard from the W3C and Google Chrome.
It was introduced in order to:

  • distinguish a PWA manifest from ordinary JSON files (config.json, for example);

  • allow servers to set the MIME type correctly based on the extension.

Specification:
https://www.w3.org/TR/appmanifest/

3. The MIME type — this is what really matters

Browsers expect the server to serve the file with the type:

Content-Type: application/manifest+json 

If the server serves application/json, Chrome and Edge will still accept it,
but they may show a warning in DevTools.

If something like text/plain is served, the manifest is ignored.

4. How to check the MIME type

Open your manifest.webmanifest in the browser and look in DevTools → Network → Headers.
The headers should contain:

content-type: application/manifest+json 

If you see application/json, it is not critical, but you can fix it in .htaccess:

AddType application/manifest+json .webmanifest 

A brief summary

Criterion .json .webmanifest
Works in browsers ✅ Yes ✅ Yes
Recommended by the standard ⚠️ No ✅ Yes
Requires the application/manifest+json MIME type ✅ Desirable ✅ Mandatory
Difference in functionality ❌ No ❌ No
Why use .webmanifest For clarity and compatibility with future standards

Recommendation

Use:


 

and set the MIME type on the server:


 
AddType application/manifest+json .webmanifest

That way it will be perfect by the standards and free of warnings in Chrome.

Will such an app, launched by the user from a shortcut, work if the site's server is unavailable or the user has no internet?

This is the most vulnerable point of a PWA if it does not use a Service Worker.

Let's work out clearly what will happen.


The scenario

The user installed the site as an app (via a shortcut).
The site now launches in a separate window (display-mode: standalone).

What happens if:

  • the server is unavailable (a 500 or 404 error, the host is not responding),

  • or there is no internet?

The short answer

The app will not launch.

When such a shortcut is launched, the browser simply tries to load the same URL (from start_url or from the installation page).
If:

  • the server does not respond,

  • there is no network connection,

  • or the page has not been loaded from cache,

then the user will see the standard browser error:

"No internet connection"
"This site can't be reached"
"ERR_INTERNET_DISCONNECTED"

Even if the app is installed, it is not self-contained without a Service Worker.

Why this is so

Without a Service Worker, the browser is not allowed to use cached versions arbitrarily.
It goes straight to the network, and if the server is unavailable, everything falls over.

A Service Worker, on the other hand, is like a "proxy inside the browser" that can say:

"No network? Then here is a saved page from the cache for you."

What definitely will not save you

Mechanism What it does Does it help offline?
manifest.json / webmanifest Only describes metadata (name, icon, colour, etc.) ❌ No
Adding to the desktop Creates a shortcut and a window ❌ No
HTTP Cache May hold resources in memory, but not reliably ⚠️ Only if you opened it recently and did not clear the cache

The browser cache may "accidentally" hold on to resources,
and the app may open offline once — but this is not reliable.

How to make it work offline

You need to add a Service Worker that:

  1. Installs on first launch.

  2. Caches the files you need (index.html, main.js, style.css, images and so on).

  3. On the next launch:

    • if there is internet — loads fresh files;

    • if not — takes them from the cache.

A minimal example:

// service-worker.js 

self.addEventListener('install', event => { event.waitUntil( caches.open('my-cache-v1').then(cache => { return cache.addAll([ '/', // the home page '/index.html', '/style.css', '/main.js', '/icon.png' ]); }) ); }); self.addEventListener('fetch', event => { event.respondWith( fetch(event.request).catch(() => caches.match(event.request)) ); }); 

And registering it in the HTML:


 

After this:
The app will be able to launch even with no network,
showing cached files or an offline page.

Conclusion

Condition Will it work offline?
Without a Service Worker ❌ No
With a Service Worker and a cache ✅ Yes
The site is opened for the first time and not cached ❌ No
The site has already been visited, but the server is unavailable ⚠️ Sometimes (if you get lucky with the HTTP cache)

Would you like me to show an example of a Service Worker that provides offline mode with a custom "You are offline" page,
so that the user sees a nice message instead of a browser error?

You said:
if "start_url": ".", what does that mean and is it valid?
ChatGPT said:

Yes, this is a good point — let's go through it in detail.


🧩 What "start_url": "." means

In manifest.json the start_url field sets the URL from which the installed app will be launched.

  • "." in a URL is a relative path pointing to the current directory of the manifest file.

  • That is, the browser will launch the app from the same directory where manifest.json is located.

An example structure:


 
/index.html /manifest.json /app/main.js

If manifest.json specifies:


 
"start_url": "."

and the manifest.json file is at the root /, then . → /
That is, the app will launch from /index.html (or /, depending on the server).

If the file is at /app/manifest.json, then . → /app/.

Validity

  • Valid. The PWA JSON specification allows relative paths.

  • The main thing is that resolving it relative to the manifest's location produces a correct URL.

Things to be aware of

  1. Depending on where the manifest is located, the result may differ.

    • If manifest.json is at the root → . = /

    • If manifest.json is in the /app/ subdirectory → . = /app/

  2. It is better to specify the path explicitly relative to the site root for unambiguity:


 
"start_url": "/"

or


 
"start_url": "/app/"
  1. Browser compatibility

    • Chrome, Edge and Opera interpret "." correctly.

    • Safari (iOS) also works, but sometimes an absolute path is better for reliability.

Conclusion

start_url What it means Recommendation
"." The manifest's current directory ✅ Works, but the relative path depends on where the file is located
"/" The site root ✅ Unambiguous and safe
"/app/" The /app/ subdirectory ✅ Controlled behaviour for a PWA

See also

  • [[b14021]]
  • Super app
  • super app
  • Single-page application
  • single page app SPA
  • MDM systems
  • Progressive Web Apps (PWA)

Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Scripting client side JavaScript, jqvery, BackBone"

Terms: Scripting client side JavaScript, jqvery, BackBone