ReadyToBinge is een trailerfeed in React en TypeScript, met Cloudflare erachter en data van TMDB. In de browser is het een progressive web app, of PWA, die je als webapp op je telefoon kan zetten. Daarnaast staat er een aparte native app in de App Store en op Google Play.
Die app kwam er omdat mensen een app in de winkel verwachten, en omdat ik een app nog altijd de betere keuze vind als mensen iets vooral op hun telefoon gebruiken. Heb je echt native dingen nodig, zoals camera, locatie of meldingen, dan kies je best een echte mobiele app. Kan het met een responsive webapp, dan volstaat die. Voor de keuze zelf is er App, webapp of website. Hier gaat het over wat de PWA technisch vraagt, en waar ze stopt.
Wat een PWA is
Een PWA is een website op HTTPS met een web app manifest en meestal een service worker. Het manifest vertelt de browser hoe de app heet, welke iconen ze heeft en hoe ze opent. Met standalone valt de adresbalk weg en lijkt ze op een gewone app. Dit is een vereenvoudigde illustratie, niet het manifest van ReadyToBinge:
{
"name": "Voorbeeldapp",
"short_name": "Voorbeeld",
"start_url": "/",
"display": "standalone",
"background_color": "#1b1510",
"theme_color": "#1b1510",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
Wat een service worker doet
Een service worker is een script dat los van de pagina draait en tussen de pagina en het netwerk zit. Het kan verzoeken van de pagina’s binnen zijn scope onderscheppen en beantwoorden uit de cache. Ook web push loopt erlangs.
De strategie hangt af van het soort verzoek. Bestanden met een hash in de naam kan je gerust cache-first serveren, omdat de naam verandert als de inhoud verandert. HTML haal je best eerst van het netwerk, met de cache als terugval, want anders blijft iemand op een oude versie van de app hangen. Data van een API hangt af van hoe vers ze moet zijn. Video laat je best met rust. Die bestanden zijn groot en werken met range requests, en een feed vol trailers zou je opslagquotum snel opgebruiken.
// Vereenvoudigde illustratie, niet de code van ReadyToBinge.
self.addEventListener("fetch", (event) => {
const { request } = event;
const url = new URL(request.url);
if (request.destination === "video") return; // de browser handelt dit af
if (request.mode === "navigate") {
event.respondWith(fetch(request).catch(() => caches.match("/offline")));
return;
}
if (url.pathname.startsWith("/assets/")) {
event.respondWith(caches.match(request).then((hit) => hit ?? fetch(request)));
}
});
Het lastigste deel is updaten. Een nieuwe service worker blijft wachten tot alle tabs of vensters met de oude versie dicht zijn, en een geïnstalleerde app blijft vaak lang open. Je kan skipWaiting gebruiken, maar dan kan een pagina die nog draait plots een nieuwe worker krijgen en bestanden vragen die er niet meer zijn. Laat oude bestanden met een hash een tijdje staan op je server, of toon de gebruiker een melding dat er een nieuwe versie klaarstaat.
Installeren
Hoe een browser beslist of je kan installeren, verandert, dus controleer de actuele regels. Chrome verwacht een manifest met onder meer een naam en iconen. De eis van een service worker met een fetch-handler verviel voor de installatie via het menu vanaf versie 108 op mobiel en 112 op desktop.
In browsers op Chromium kan je het event beforeinstallprompt opvangen en op een zelfgekozen moment een eigen knop tonen. Dat event is geen standaard, en Safari heeft het niet.
Op iOS installeert iemand via het deelmenu en “Zet op beginscherm”. Je kan dat niet vanuit je code starten, dus je zal het zelf in je interface moeten uitleggen. Met de mediaquery display-mode: standalone kan je nagaan of de app al geïnstalleerd draait, en die uitleg dan verbergen.
Dat is een stap meer dan in een appstore, waar mensen gewend zijn op een knop te drukken. Daar staat tegenover dat een link meteen werkt: stuur je iemand een link naar een pagina, dan opent die in de browser, zonder installatie.
Wat iOS beperkt
Apple past dit regelmatig aan, dus test op echte toestellen en controleer de documentatie van WebKit. Op het moment van schrijven gaat het hierover:
- Web push werkt sinds iOS 16.4, alleen voor apps die op het beginscherm staan. De vraag om toestemming moet komen na een actie van de gebruiker. Een bezoeker die de app niet geïnstalleerd heeft, kan je geen melding sturen.
- De Background Synchronization API wordt niet door elke grote browser ondersteund, en Safari ondersteunt ze niet. Buiten het verwerken van een pushbericht reken je er best niet op dat je code draait als de app dicht is.
- De opslag van een site kan verwijderd worden als de gebruiker die site een tijd niet gebruikt heeft of als het toestel plaats nodig heeft. Of een verzoek om blijvende opslag ingewilligd wordt, hangt mee af van of de app op het beginscherm staat. Behandel lokale data als iets dat je opnieuw kan ophalen.
- Reken er niet op dat Safari en de geïnstalleerde app hun opslag delen. Wie in Safari ingelogd was, moet na het installeren mogelijk opnieuw inloggen.
- Video speelt alleen vanzelf af als ze gedempt is en
playsinlineheeft. Met geluid vraagt ze een actie van de gebruiker. Voor een trailerfeed bepaalt dat hoe de eerste seconden eruitzien. Android en desktopbrowsers hebben hun eigen regels voor autoplay.
API-sleutels
Alles in een PWA kan de gebruiker downloaden en lezen, dus een API-sleutel in je front-end is een publieke sleutel. Dat geldt ook voor TMDB. Een kleine server ertussen lost dat op en kan meteen antwoorden cachen, en met Cloudflare is een Worker daar een voor de hand liggende plek voor.
Wat je wint en wat je inlevert
Je hebt één codebase voor iPhone, Android en desktop. Je zet een update online zonder keuring, en je gebruikers moeten daarvoor niets installeren. Elke pagina heeft een link die je kan delen.
Met alleen een PWA sta je niet in de appstore, waar mensen apps gaan zoeken. Op iOS heb je de beperkingen hierboven. Voor toegang tot het toestel ben je afhankelijk van wat de browser aanbiedt, en een native app heeft meer toegang. Hoe je die afweging voor een bedrijf maakt, staat in Heb je de appstore wel nodig?
Bij ReadyToBinge staat daarom ook een native app in de winkels, naast de PWA.
Lees ook
Wat is jouw idee?
Vertel me wat je wil bouwen. Ik probeer binnen één werkdag te antwoorden.