Build a Linktree alternative

Build a Linktree alternative as a links list, an avatar, and one theme. Put a URL you own on the internet and cancel the $9–$24 a month bio page.

  • getting-started
  • vibe-coding
  • tonight

You can build a Linktree alternative tonight. Linktree is a page of links. The catalog says that in one line, and it is the whole app most people use.

You pay $9–$24 a month for it. That is $108–$288 a year. The basic replacement is 30 minutes: a links list, an avatar, and one theme.

I would ship this one first if I still rented a bio page. It is marked Tonight. The verdict on the card is “Absolute no-brainer”.

Yesterday I wrote about why you should not rebuild Notion first. Tonight is the follow-up. Open the product page, copy the prompt, and put a URL on the internet before you get bored.

What you ship when you build a Linktree alternative

The Linktree page splits the work into three rows.

The first row is the one you want tonight. Links, avatar, one theme. A public page with your face, a short bio, and the five URLs you already paste into every bio field.

The second row is 2 hours. Drag-to-reorder, drafts, daily click counts. Those can wait until the page is already live.

The third row is a week. Multi-tenant hosting, billing, a theme marketplace, analytics. The table labels that row as the part you should skip.

If you tell an agent to “clone Linktree”, it aims at the third row. Use the prompt on the page. It is written for a useful v1, and you still have to keep it on the first row.

One theme means one set of colors and fonts that looks like you. Pick two colors and a typeface. Skip the switcher and the gallery. The marketplace is how Linktree became a platform, and you need a page.

Here is the shape of the data you are building. Nothing else has to exist at 30 minutes.

const profile = {
  slug: 'flavio',
  name: 'Flavio Copes',
  bio: 'I write about JavaScript and tiny software.',
  theme: 'ink-on-paper',
  links: [
    { label: 'Newsletter', url: 'https://flaviocopes.com' },
    { label: 'YouTube', url: 'https://youtube.com/@flaviocopes' },
    { label: 'X', url: 'https://x.com/flaviocopes' },
  ],
}

A visitor hits a stable public URL and sees that list. You change a label, publish again, and the Instagram bio still points at the same address.

Who a Linktree clone is for

The audience line on the page is specific. One creator managing a single public profile.

The goal is to publish a fast, branded bio page and understand which links get clicked. Tonight you can skip the “which links” part. Publishing the page is the done state.

If you sell bio pages to clients, or you need a fleet of branded profiles, keep paying. That is the honest rebuild. The site puts it at a week on purpose.

The same v1 also replaces Beacons and Bio Link. They solve the same job. One focused build covers whichever of those you currently pay for.

Read the non-goals before you copy anything

Open the guide. Read the audience and the non-goals before you touch the prompt.

The non-goal is the useful sentence: do not build multi-tenant billing, a theme marketplace, or enterprise analytics.

The guardrail sits next to it. Never collect visitor fingerprints or precise location data. A page of links does not need to know who clicked.

Then pick the stack you already write. Every prompt ships in four flavors: AHA (Astro, HTMX, Alpine.js), Next.js, Laravel, and Rails. The AHA version for this product uses Astro, Alpine, and D1. This site is an Astro app on Cloudflare, so that is the path I default to. Use Rails if that is your weekday language. The scope matters more than the framework.

Copy the prompt. Before you run it, add one extra line at the top: ship the 30-minute row only. Links list, avatar, one theme. Stop.

Walk the first Linktree journey

The prompt names three end-to-end journeys. Tonight you need the first one.

You sign in. You complete a profile. You add three links. You preview the mobile page. You publish to a stable public URL.

Keep that URL stable when you edit a link later, so the Instagram bio and the YouTube description keep working.

The screens the prompt asks for are a dashboard, a profile editor, a link editor, and an analytics view. For 30 minutes you can live with the first three, and analytics can be a stub or a missing page.

The dashboard needs the public URL, a last-published time once you have one, and a clear edit action. It does not need a sparkline.

The profile editor is the whole app for the owner. Avatar upload, display name, short bio, theme controls, the link list, and a phone-sized live preview. The prompt wants social handles and inline validation too. Handles can be three icon links under the bio. Validation is “this field is required” and “this URL is not http or https”.

The link editor is a label and a destination. Visibility, highlight styles, and drag handles belong to the 2-hour row. A static order, the order you typed them, is fine for v1.

The data model on the page is Profile, Link, ClickEvent, Theme. Tonight you need Profile and Link. Theme can be a handful of CSS variables on the profile row. ClickEvent waits.

One more rule from the prompt, and it is a good one. Allow exactly one profile in v1. Reserve its slug during setup. You are not building a hosting company.

The one piece of code worth writing yourself

Agents invent URL fields that accept anything. The public page then becomes an open redirect with your face on it.

The prompt already says to normalize URLs and reject unsafe protocols. The mini-prompt on the page, “The link field that refuses javascript: politely”, is the longer version of that rule.

The short version is enough for tonight. Humans paste instagram.com/flaviocopes with no scheme. Attackers paste javascript: with whitespace in the middle. You assume https for the first case and reject everything that is not http or https.

function normalizeLink(raw) {
  const trimmed = raw.trim()
  const withScheme = trimmed.includes('://') ? trimmed : `https://${trimmed}`
  const url = new URL(withScheme)

  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
    throw new Error('Only http and https links are allowed')
  }

  url.username = ''
  url.password = ''
  url.host = url.host.toLowerCase()
  return url.href
}

normalizeLink('instagram.com/flaviocopes')
//https://instagram.com/flaviocopes

A failed save should leave the rest of the draft intact. Do not wipe the bio because link three is missing a slash.

Publish is a snapshot if you have ten extra minutes

The second journey on the page is the one people skip, then regret.

You edit a published link. The public URL stays the same. You see the unpublished change in preview. You press publish on purpose.

If save and publish are the same button, a typo goes live while you are still typing the next label. For a page that sits in every bio you have, that gets old fast.

The prompt already keeps draft and published snapshots separate. Editing cannot alter the live page by accident. The mini-prompt “Publish is a snapshot, not a save button” is the evening-length version: copy the draft into an immutable snapshot, point the live page at it in one step, keep a short history.

Skip that machinery if the 30 minutes are real. A single published profile still beats a $9–$24 subscription. Add the snapshot the first time you almost shipped a half-edited bio.

What not to build in a Linktree clone

Leave out the theme marketplace, billing, guest accounts for visitors, and anything that looks like an analytics suite. The non-goal on the page already names multi-tenant billing, a theme marketplace, and enterprise analytics. The guardrail already bans visitor fingerprints and precise location. A list of URLs does not need a consent banner.

Social icons are on the feature list. One row under the bio is a few image links. Featured-link styling is a CSS class on the first item. Neither of those is a second product.

If the agent starts a template gallery, stop it. Paste the non-goal again. The week-long row exists so you have a sentence to point at.

The 2-hour row, after you use the page

Use the page as the real bio for a week. Then look at the second estimate.

Drag-to-reorder. Each link gets an integer position. On drop, rewrite the affected rows in one transaction. The mini-prompt is “Drag-to-reorder that doesn’t corrupt on the third drag” because float positions rot and a crash mid-update can leave two links claiming position 3. Keyboard users get move-up and move-down buttons that hit the same endpoint.

Drafts, so an edit stays off the public page until you publish.

Daily click counts by link. The feature list says privacy-friendly daily totals. The integration on the page is Cloudflare Web Analytics if you want a page-level view without rolling your own. If you want per-link counts, the mini-prompt is “The click counter that stores nothing about the clicker”. Record the link id and the UTC day. Increment a (link_id, day) row. No cookies, no IPs, no fingerprint. The redirect should stay fast. The count can write after the visitor is already gone.

That is the solid v1. The catalog says 2 hours. Stop there.

Host it, point the bios, cancel

The product pages put hosting this way: a focused replacement costs your build time plus close-to-zero hosting. That assumes you are not rebuilding the marketplace.

Put the page on a subdomain you already own, links.flaviocopes.com, or on a path next to a site you already deploy. Update the Instagram bio first, then the other places you still point at the old Linktree URL.

Use it for a week as the real tool, not as a demo. If you miss click counts, add them from the 2-hour row. You will not miss a theme marketplace.

Then cancel Linktree, or Beacons, or Bio Link, whichever one you pay for.

A whole personal site with stacked sections and a contact form is a different job. That is Carrd, $2–$4 a month, about 2 hours. Build the list first if a list is the rent you are paying. A Carrd page is a landing page, and you can do that later.

If you want another Tonight card after this one, the audit is still the index. TinyPNG Pro is about 60 minutes and stays in the browser. The TinyPNG alternative is that walkthrough. Bitly is about 90 minutes, a short URL and a click count.

Start now

Go to Build your own Linktree. Copy the AHA prompt, or the stack you already write.

Tell the agent the first estimate only: links list, avatar, one theme. Thirty minutes.

Publish the URL. Point one real bio at it tonight.