Skip to content

A client's site on the client's own domain

A client asks you for a simple page: their name, what they do, their opening hours, and how to reach them. They already own a domain. This takes you from an empty folder to that page, live at www. their domain, with nothing to build and no server to look after.

It uses the landing-page template. The whole page is one JavaScript file, so a change is an edit and a deploy.

  • Node 22.13 or newer. npx comes with it, so there is nothing to install.
  • A wawesome workspace on the Solo plan or above. Custom domains start there, at €9 a month, with three Apps. The pricing page has the rest. On the Free plan you can follow along without the domain, and the site answers at its wawesome address.
  • Access to the DNS settings of your client’s domain. You add three records for each half of the name.

Yours will read differently:

Here Where it comes from
Workspace address jo-studio npx wawesome workspace
Client Fernwood Ceramics the made-up studio the template ships with
App fernwood the name of your folder, unless you type another
Function root you type it in step 1
Domain www.fernwood.example your client’s domain

Make one folder for this client and start from the template:

mkdir fernwood && cd fernwood
npx wawesome init --template landing-page

If you aren’t logged in, it offers to log you in first. Then it asks two names:

[wawesome] Fetching Landing Page (landing-page)...

  Function name? (home): root
  (an App groups the Functions of one project, and its slug is part of the public URL)
  App slug? (fernwood):

[wawesome] ✅ Scaffolded 11 files from 'landing-page'.

Type root for the Function name. A Function called root answers at the App’s own address, so the page is at / and not at /home. That matters once the domain is on, because a visitor to your client’s domain lands on /.

Press enter for the App slug. One App per client keeps each client’s site, settings and domain apart from the others.

Then the CLI shows the address the page will answer at:

[wawesome] Your Function will answer on:

    https://jo-studio--fernwood.wawesome.app
    Every path beneath this address reaches the Function.

If this is your first deploy, it also offers to change your workspace address. That’s the jo-studio part. Every client’s wawesome address carries it, so pick a name you’re happy to show clients. After the first deploy you can still rename once every 30 days, and the old address redirects for 90 days. Press enter to keep it.

The template needs no keys, so it deploys straight away:

======================================================
🚀 DEPLOYED SUCCESSFULLY!
======================================================

  App:        fernwood
  Function:   root
  Version:    1
  Handler:    dist/index.js
  Visibility: public

  URL:        https://jo-studio--fernwood.wawesome.app
              Every path beneath this address reaches the Function.

  Plan:     Solo. This deploy does not change your bill.
  Apps:     1 / 3 slots
  Storage:  0 B / 5 GiB
  Usage:    September 2026 (UTC)
            Invocations          0 / 250k      (0.0%)
            Caller-facing bytes  0 B / 50 GiB  (0.0%)
======================================================

Open the URL. You’ll see a page for Fernwood Ceramics, the studio the template ships with. It’s live, and it’s the one we’ll turn into your client’s.

2. Put your client’s words and domain in

Section titled “2. Put your client’s words and domain in”

Everything on the page comes from the site object at the top of src/index.js:

export const site = {
  name: "Fernwood Ceramics",
  tagline: "Hand-thrown stoneware, made to be used every day",
  intro:
    "A two-person studio on the edge of Dartmoor. ...",
  cta: { label: "Ask about a commission", href: "mailto:hello@fernwood.example" },
  sections: [ /* three cards */ ],
  contact: { /* opening hours */ },
  footer: "Fernwood Ceramics, Ashburton, Devon",
};

Change the words. Point cta.href at your client’s email address. Add or remove a card in sections. The styles are in the same file, in styles(), if you want to change the colours.

The footer has a “Built with wawesome” line. Delete it if you like. We add nothing to what the page returns, so it won’t come back.

Now add the client’s domain. It’s one line in wawesome-function.json:

{
  "app": "fernwood",
  "function": "root",
  "entry": "src/index.js",
  "domain": "www.fernwood.example"
}

On the Free plan, leave that line out. The page still goes live at its wawesome address.

Deploy the words and the domain together:

npx wawesome deploy

The deploy makes version 2, attaches the domain, and prints the records to add:

======================================================
🚀 DEPLOYED SUCCESSFULLY!
======================================================

  App:        fernwood
  Function:   root
  Version:    2
  Handler:    dist/index.js
  Visibility: public

  URL:        https://jo-studio--fernwood.wawesome.app
              Every path beneath this address reaches the Function.

  Domain:     www.fernwood.example: coming up, and each half of the name is below

              fernwood.example: not verified yet, so add the records below
              www.fernwood.example: not verified yet, so add the records below

  DNS records to add at your registrar:

    fernwood.example:

      Add this first. It proves you own the name:
        TXT    _cf-custom-hostname.fernwood.example  3f6c2a91-7d4e-4b08-9c15-e2a8b0d4f731

      Add this first too. It issues the certificate and keeps it renewed:
        CNAME  _acme-challenge.fernwood.example      fernwood.example.5c0e9b2d71a4f386.dcv.cloudflare.com
        Delete any _acme-challenge TXT record already at that name.

      Add this once the certificate is active. It points traffic here:
        CNAME  fernwood.example                      wawesome.app

    www.fernwood.example:

      Add this first. It proves you own the name:
        TXT    _cf-custom-hostname.www.fernwood.example  b81d4e07-2c9a-4f63-a5e0-6d3f18c92b4e

      Add this first too. It issues the certificate and keeps it renewed:
        CNAME  _acme-challenge.www.fernwood.example      www.fernwood.example.5c0e9b2d71a4f386.dcv.cloudflare.com
        Delete any _acme-challenge TXT record already at that name.

      Add this once the certificate is active. It points traffic here:
        CNAME  www.fernwood.example                      wawesome.app

              The deploy is finished. This domain comes up on its own once the
              records resolve.

  Plan:     Solo. This deploy does not change your bill.
  Apps:     1 / 3 slots
  Storage:  0 B / 5 GiB
  Usage:    September 2026 (UTC)
            Invocations          2 / 250k        (0.0%)
            Caller-facing bytes  9 KiB / 50 GiB  (0.0%)
======================================================

You named www.fernwood.example, and the bare fernwood.example came with it. People type both, so the site answers at both. Each half has its own records.

Copy the values from your own output, not from this page. Yours are different. If you lose them, npx wawesome domains prints them again.

Refresh https://jo-studio--fernwood.wawesome.app and your client’s words are there. Send that address to your client now. They can read it on their phone while you sort out their DNS, and each change they ask for is an edit and a deploy.

That address keeps working after the domain is on. Nothing you send now breaks later.

Log in to wherever your client’s DNS lives. That might be their registrar, or Cloudflare, or a host they used before.

First round. Add the TXT and the _acme-challenge CNAME for each half, so four records. They prove the name is your client’s and get a certificate for it. They don’t move any traffic, so the old site, if there is one, keeps answering while you wait.

Then check where it got to:

npx wawesome domains

We check once a minute. When both halves read certificate issued, so point the CNAME here to cut over, the certificates are ready.

Second round. Add the two traffic CNAMEs, the ones pointing at wawesome.app. This is the cutover: once they resolve, visitors to your client’s domain reach your page. Add them last, so a visitor never lands on a name that has no certificate yet.

Two things catch people here:

  • Cloudflare’s orange cloud. Cloudflare turns its proxy on for every new CNAME. Click the cloud on the two traffic records so it turns grey and reads DNS only. With the proxy on, the domain never comes up. npx wawesome domains tells you when it sees this.
  • The bare name. Some DNS providers won’t hold a CNAME at the bare fernwood.example. Then that half reads not claimed and names the provider, and the www. half still works. Most providers that do allow it call it CNAME flattening or an ALIAS record.

Custom domains has every state the domain moves through, and what each one asks of you.

npx wawesome domains
[wawesome] The domain on app 'fernwood':

  Domain:     www.fernwood.example: serving at both halves of the name
              Last checked 2026-09-25 10:42 UTC.

              fernwood.example: serving
              www.fernwood.example: serving
              This app's own address answers too, so nothing pointed at it breaks.

Only serving means a visitor reaches the page at that name. Ask for it yourself:

curl -I https://www.fernwood.example
HTTP/2 200
content-type: text/html; charset=utf-8

That’s the job done. Your client’s page answers at their own domain, with a certificate we renew for you, and the wawesome address still works.

One App for this client, with one Function and one domain. From here:

  • A change to the page is an edit to src/index.js and npx wawesome deploy. The domain stays on through every deploy.
  • If you ship something broken, npx wawesome version switch <version> puts an earlier version back on the address. The CLI reference covers versions and rolling back.
  • If the client wants a form rather than a mailto: link, start the next project from the contact-form template. It saves each message to a table in the client’s own Supabase project.
  • If you’d rather let an AI agent make the next change, connect it over MCP. It can deploy to this App and read its logs.
  • The Solo plan holds three Apps, so that’s three clients. Pricing has the plans with more.