Dev.to WebDev 🛠 Dev 👁 0 📖 5 min read

We planned an app and built a website

Part 1 of the origin story of Tintly. New here? The first post explains the series. The first design document for Tintly describes a .NET backend with two API hosts. Next to the public one, the project layout says: C

Part 1 of the origin story of Tintly. New here? The first post explains the series.

The first design document for Tintly describes a .NET backend with two API hosts. Next to the public one, the project layout says:

ColoringApp.Api.Public         Host for iOS/Android apps
ColoringApp.Api.Admin          Host for the React admin portal

The same document has a long "out of scope, for later" list: subscriptions through App Store and Play billing, push notifications, Google and Apple sign-in, and a managed container setup on a big cloud provider. It's the plan for a mobile app with a backend behind it.

Then we wrote a second design document. Its goal, in one sentence:

A Next.js website where a guest can find a free coloring page, color it in the browser, close the tab, come back and continue, mark it complete and download it, with public pages that are server-rendered and indexable.

Tintly has been a website ever since. There is still no app.

What the second plan was built around

We didn't write down why we changed our minds, and we're not going to invent a story now. What we did write down is what the new plan had to do, and every line of it is something an app makes harder.

Nothing between a visitor and the first tap. Our product rules say it in one line: "No app, no ads, no account needed." An app has a store page, a download and a first launch before anyone colors anything. A web page has one tap.

No account to keep your work. The guest's progress lives in the browser (IndexedDB) and survives closing the tab. Signing in is optional and only syncs your pictures between devices. Nobody has to give us an email to color a picture.

Pages a search engine can read. "Server-rendered and indexable" is in the goal sentence for a reason. Every coloring page is its own URL with its own title and description. A screen inside an app can't rank for "cozy cat coloring page". A web page can.

One codebase instead of three. One Next.js site works on phones, tablets and laptops. The alternative was an iOS app, an Android app and still a website for the admin side.

None of these are unusual reasons. What surprised us later is how much of the first plan they removed.

What the switch cost

Less than we expected, because the backend was the part we'd built first, and most of it didn't care who was calling.

The public API kept its name and its endpoints. The comment that said "Host for iOS/Android apps" is out of date, and the code under it barely changed. The website's server calls the same endpoints an app would have called.

The biggest thing that disappeared was the money plan. App Store and Play billing were the first idea for paid content. A website can't use either, so paid books went through a web checkout later. That's a separate post ("Free pages stay free").

Push notifications went too. The web replacement is an optional email when new pages come out.

What we kept for an app that may never come

Two decisions from those first designs still assume a mobile app might exist one day. One of them was cheap and we'd make it again. The other one we should have cleaned up.

The cheap one is how a freehand brush stroke is stored:

/**
 * A committed freehand stroke. Coordinates and size are in SVG viewBox units,
 * so a stroke replays identically at any zoom level or device size.
 */
export interface Stroke {
  readonly id: string;
  readonly tool: StrokeTool;
  readonly color: string; // #RRGGBB
  readonly size: number;
  readonly opacity: number; // 0–1
  readonly points: readonly number[]; // [x0, y0, x1, y1, …]
}

Strokes are saved in the picture's own coordinate system, not in screen pixels. The original reason was "so an iOS or Android app can replay them later". The reason it matters today is more ordinary. Someone starts a picture on a phone, signs in on a laptop, and the brush strokes land in exactly the same place on a screen three times wider. The same rule makes zoom work without any special cases. We'd have needed this even if the word "app" had never come up.

The one we should have cleaned up is on the home page. The first web design had a "get the mobile app" section with placeholder store badges. It's no longer on the page, but the component is still in the repo, imported by nothing:

web/src/components/home/AppPromo.tsx
web/src/components/layout/StoreBadges.tsx

It's harmless, but it's a small lie the codebase keeps telling anyone who reads it. Deleting it is on the list.

Would we start with the app again?

No. Three things we've learned since launch make the web choice look better than it did at the start:

  • About three in four of our visitors are on a phone, usually arriving from a shared link. They're coloring within seconds. An app store page in the middle of that would lose most of them.
  • Search is our long-term bet, and it only works because each picture is a real page. There's nothing for Google to index inside an app.
  • The people we're building for want a short, calm break. Installing something is the opposite of that.

The honest counterpoint: an app icon on a home screen brings people back, and getting people to come back is one of our hardest problems right now. If we ever build an app, it will be for the people who already color with us every day, not to find new ones.

What we'd tell someone else

Write the first plan, then argue with it before the code makes the argument expensive. Our pivot was cheap because the only thing built was a backend that didn't know what a phone was.

And keep your API ignorant of the client. "Host for iOS/Android apps" was a comment, not a design. Because the endpoints never assumed a device, swapping an app for a website changed the front end and almost nothing behind it.

Next in the series is a field notes post: 389 people started a picture, and 10 finished it.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.