← Writing

Build log · 31 July 2026

Install this site on your iPhone (no App Store involved)

The //A mark in a pink-to-orange gradient, glowing on black: the home-screen icon this site generates at build time.

I am not building an iOS app. Not out of principle, out of math. An app means Xcode, App Store review, a $99/year membership, and a release train that moves in days. This site deploys in about a minute, several times a day, every time I push. The moment I wrap it in an app, the app is the stale copy of a thing that updates itself.

But I still want it on the home screen, full-screen, with its own icon, opening like an app instead of a browser tab. That exact thing exists, it has since forever, and it takes three small files: a web app manifest, an Apple touch icon, and two lines of iOS metadata. Here is the whole build, copied from this site’s actual source.

~1

minute to ship

every push, straight to prod

0

review queues

no store between us

$0

per year

the membership stays unpaid

2

meta lines for iOS

the part Apple reads

What you get, what you give up

Installed this way, the site gets its own icon on the springboard, its own card in the app switcher, and a standalone window with zero Safari chrome: no URL bar, no tabs, just the page. Updates ship by deploying the site; nobody taps “update”, nobody reviews anything. Since iOS 16.4 an installed web app can even do push notifications if you ever wire them up.

What you give up: App Store discovery (irrelevant, my distribution is TikTok and X, not search-in-a-store) and the deeper native APIs I was never going to use. For a content-and-commerce site, the trade is not close.

push to prod · the phone keeps up

01

manifest.ts

name, icons, display mode

02

Icon at build

one generated gradient mark

03

Two iOS meta lines

the part Apple actually reads

04

Add to Home Screen

no store, no review, no $99

Step 1: the manifest

The manifest is what makes a browser treat your site as installable. In Next.js it’s one typed file at app/manifest.ts: the framework serves it and links it from every page. display: 'standalone' is the line that removes the browser chrome; black background and theme colours keep the launch moment seamless on a black site.

// app/manifest.ts
import type { MetadataRoute } from 'next'

export default function manifest(): MetadataRoute.Manifest {
  return {
    name: 'Alex Saint',
    short_name: 'Alex Saint',
    description: 'I make viral AI video, and I sell the prompts behind it.',
    start_url: '/',
    display: 'standalone',
    background_color: '#000000',
    theme_color: '#000000',
    icons: [
      { src: '/icon.svg', sizes: 'any', type: 'image/svg+xml' },
      { src: '/apple-icon', sizes: '180x180', type: 'image/png' },
    ],
  }
}

Step 2: the home-screen icon

iOS ignores SVG touch icons, so the home-screen icon has to be a PNG. Instead of exporting one from a design tool and letting it rot, Next renders it at build time from JSX: app/apple-icon.tsx returns an ImageResponse, and the //A mark is drawn from the same coordinates as the favicon. One source of truth, two icons.

// app/apple-icon.tsx
import { ImageResponse } from 'next/og'

export const size = { width: 180, height: 180 }
export const contentType = 'image/png'

export default function AppleIcon() {
  return new ImageResponse(
    (
      <div style={{ width: '100%', height: '100%', display: 'flex',
                    alignItems: 'center', justifyContent: 'center',
                    background: '#000000' }}>
        <svg width="180" height="180" viewBox="0 0 100 100">
          {/* the //A mark, pink → gold */}
        </svg>
      </div>
    ),
    size
  )
}

One trap: don’t pre-round the corners. iOS applies its own superellipse mask to every home-screen icon, so a pre-rounded icon shows dark notches where the two masks disagree. Ship it full-bleed square and let the OS do the rounding.

Step 3: tell iOS it’s an app

Two additions to the root layout’s metadata finish the job. appleWebApp opts the site into standalone mode under its own name, and the viewport’s themeColor keeps the status bar black so the app edge-to-edge matches the page.

// app/layout.tsx
export const metadata: Metadata = {
  // ...
  appleWebApp: {
    capable: true,
    title: 'Alex Saint',
    statusBarStyle: 'black-translucent',
  },
}

export const viewport: Viewport = {
  themeColor: '#000000',
}

black-translucent means the page flows under the status bar, which looks incredible on a black site and terrible on a white one. If your background isn’t dark, use 'default'.

Installing it

Chrome’s own walkthrough of what installation does on each platform is the clearest one I have found, and it is worth two minutes before you ship a manifest of your own.

  1. 01Open alexsssaint.com in Safari on your iPhone.
  2. 02Tap the Share button, the square with the arrow.
  3. 03Tap "Add to Home Screen". The //A icon and the name come from the files above.
  4. 04Tap Add. Launch it from the home screen: full-screen, black status bar, no browser.

The one thing to design for

Standalone mode has no back button and no refresh: your site’s own navigation is all the navigation there is. That is why every page here carries the fixed avatar chip in the top corner: in the installed app it’s not decoration, it’s the way home. If your site leans on the browser’s back button, fix that before you ship a manifest.

That’s the entire build. No App Store, no review queue, no second codebase. The site is the app, and every git push is a release.