Build my web app

Worked example: a restaurant menu website becomes an installable web app in an afternoon

Every decision for one realistic site — a 12-page restaurant site on shared hosting — from choosing the caching mode to the QR code on the tables and the first push notification.

Guides explain one thing at a time. This one follows a single, realistic site through every decision, with the reasoning. The site: a neighbourhood restaurant's twelve-page website on ordinary shared hosting — menu, opening hours, location, gallery, an online ordering page provided by a third-party service, and a news page used twice a year. WordPress, one plugin for the menu, an SSL certificate that came with the hosting.

Hour 0: is this worth doing?

The owner's question was "should I get an app?" — meaning pay someone thousands for a native app. The honest analysis:

Verdict: installable web app, no native build. Possibly a Google Play listing later if customers ask "is it on the Play Store?".

Hour 1: fix the site first

Opened on a phone, the site had two problems that would become the app's problems: a hero image of 3.2 MB and a "reservations" link to a page that had been removed (404). Compressed the image to 180 KB, fixed the link. Checked that every page loads over HTTPS with no mixed-content warnings — one gallery image was hard-coded http://; fixed. Total: twenty minutes, and the site got faster for everyone, app or not.

Hour 1.5: assets

Hour 2: generate and deploy

In the PWA Builder: URL, names, colours, icon. Caching mode: Balanced — the menu changes seasonally, prices occasionally, and stale-for-one-visit is acceptable, while the ordering page must always be live. Left OneSignal blank for now.

Downloaded the zip. Uploaded manifest.json, sw.js, offline.html and icons/ to public_html via the host's file manager. Added the head snippet with a snippet plugin. Opened /sw.js in a browser — JavaScript, good. Opened /manifest.json — JSON, good.

One edit to sw.js: the ordering page is a third-party page embedded in an iframe from another domain, so nothing to exclude there; but the WordPress admin paths were added to the never-cache list as the WordPress guide recommends.

On a phone: Chrome offered Install from the menu immediately. Installed. Opened from the icon: cream splash, red status bar, menu page, no browser bar. Aeroplane mode: menu and hours (visited) loaded; gallery (not visited) showed the cream offline page with the logo. Exactly as intended.

Hour 3: the install page and the tables

Created /app following the install page pattern: icon, "Menu, hours and ordering — on your home screen, even with no signal downstairs", an Install button for Android, the three share-sheet steps for iPhone, two framed screenshots from the screenshot tool.

Generated a QR code pointing at /app?utm_source=qr&utm_medium=table, dark red on cream, restaurant initial in the centre, caption "Scan for the menu — no app store needed". Printed at 35 mm on the table cards and 12 cm on the door. Scanned the print test with two phones before ordering. A second code for the receipts pointed at utm_medium=receipt.

Week 2: the first notification

With installs happening, the owner wanted to announce a new dish. Set up OneSignal, regenerated the service worker with the OneSignal App ID (bumped the cache version), redeployed. Added a pre-prompt to the install page and the menu page for installed users only: "Hear about new dishes and specials — about twice a month?" with a Yes button. iPhone users see it only inside the installed app, as iOS requires. First campaign: one notification, deep-linked to the new dish on the menu page, sent at 11:30 on a Thursday. Opt-outs: a handful. Orders that lunchtime: noticeably up. The owner now understands push as a promise about frequency, and has kept to twice a month.

Month 2: the Play Store question

A few customers asked whether it was "on the Play Store". Because the site was already a valid PWA, listing it as a Trusted Web Activity took an evening: Android option in the builder for the test APK and assetlinks.json (hosted under /.well-known/), Bubblewrap for the AAB from the same manifest, a US$25 developer account, screenshots from the installed app, the checklist for the forms. Closed testing with staff and regulars for the required 14 days, then production. iPhone customers continue to install from the site; nothing changed for them.

What it cost

ItemCost
Builder, tools, guidesFree
Hosting changesNone
Printing table cardsSame cards they already reprint seasonally
OneSignalFree tier
Google Play account (optional)US$25 once
TimeAbout an afternoon for the app; an evening for Play

What would have been different

Your site is probably closer to this example than to anything in a native-app agency's portfolio. Start with the builder; the rest of the afternoon is described above.