# Deployment

## Deployment & Launch

Homio builds a static site into `dist/`, so it runs on any static host. It ships ready-made settings for **Netlify** (`netlify.toml`), **Vercel** (`vercel.json`, `vercel.sh`), and **Cloudflare Pages** (`wrangler.toml`).

| Setting           | Value                     |
| ----------------- | ------------------------- |
| Build command     | `npm run build`           |
| Output directory  | `dist`                    |
| Node.js version   | 22.12 or later            |
| Package manager   | npm (`package-lock.json`) |

### Before You Launch
Go through this list once. Each item links to the page that explains it.

**Address and identity**

- [ ] `[site] baseUrl` is your real address, not the shipped `https://example.com` placeholder, and `npm run build` shows no `[site]` warning ([SEO & AI Visibility](/homio/customization/seo-ai-visibility/))
- [ ] `title`, `tagline`, `description`, and `logoText` in `[site]`, and `author` and `keywords` in `[seo]`, describe your business. The tagline is added to every page title ([Config Reference](/homio/customization/config-reference/))
- [ ] The logo, favicons (`npm run generate-favicons`), social image (`npm run generate-og-image`, after the two items above), colors in `theme.css`, and fonts in `fonts.json` are yours ([Site Identity & Logo](/homio/customization/site-identity-logo/), [Colors & Fonts](/homio/customization/colors-fonts/))

**Business details**

- [ ] `[business]` in `config.toml` has your name, business type, phone, email, address (or `show = false` if you have no public address), opening hours, time zone, and closed dates, and the build prints no `[business]` warning ([Business Info](/homio/customization/business-info/))
- [ ] The sample service areas in `src/content/areas/` are replaced with yours, with real postal codes, and the area links in the **Pages** menu point to them ([Service Areas](/homio/customization/service-areas/))
- [ ] `zipPattern` under `[settings.booking]` matches your country's postal codes

**Forms**

- [ ] `contactFormAction` and `contactFormProvider` post to your own account (Formsubmit, Formspree, or Netlify Forms), and the build prints no `[forms]` warning ([Forms](/homio/customization/forms/))
- [ ] `formAction` under `[settings.booking]` is set if booking requests go to a different inbox ([Booking Request](/homio/customization/booking-request/))
- [ ] The Mailchimp `formAction` under `[settings.subscription]` is your list, or `enable = false` there if you don't send a newsletter
- [ ] The careers apply address (`mailto:jobs@example.com` in `src/content/careers/english/-index.md`) is yours, or the sample roles are removed

**Prices and sample content**

- [ ] Every price, frequency discount, add-on, and estimator rate in `src/content/pricing/` is yours, with the same numbers in English and French (no `[pricing]` warning) ([Pricing & Estimator](/homio/customization/pricing-estimator/))
- [ ] `currency` under `[settings.estimator]` is yours
- [ ] The services and their included lists match what your crews really do ([Services & What's Included](/homio/customization/services-included/))
- [ ] Sample facts are replaced: reviews and ratings, insurance amounts, team members, projects, job roles, blog posts, and the Re-clean guarantee terms. They are marked `# sample` in the content files ([Reviews & Projects](/homio/customization/reviews-projects/), [Collections](/homio/customization/collections/))
- [ ] The review links in `src/content/sections/english/trust-strip.md` (and `french/`) point to your real review profiles, not `https://example.com/review`
- [ ] Every sample photo is replaced. `IMAGE-CREDITS.md` lists them all ([Icons & Images](/homio/customization/icons-images/))
- [ ] A project-wide search for `example.com` and `555` finds nothing you still need to change

**Pages and menus**

- [ ] The privacy policy, terms, cookie policy, and cancellation policy in `src/content/pages/english/` and `french/` are rewritten for your business and reviewed. They ship as sample text with a notice at the top
- [ ] The **Pages** demo menu is hidden (`"enable": false` in both menu files), and the menus point at your pages ([Menus](/homio/customization/menus/))
- [ ] The demo homepages you don't use are deleted ([Homepages](/homio/customization/homepages/)), and `/elements/` and `/components/` are removed if you don't want the layout and shortcode galleries online ([Pages](/homio/customization/pages/))
- [ ] `[settings.layoutSwitcher] enable = false`, and `PUBLIC_DEMO_MODE` is not set in your host's environment variables
- [ ] `footer.description` and `footer.copyright` in `src/i18n/en.json` and `fr.json` describe your business, and the links in `src/config/social.json` are yours

**Analytics and checks**

- [ ] Analytics tags are in `[head]`, marked with `data-consent` while the cookie banner is on ([Cookies & Analytics](/homio/customization/cookies-analytics/))
- [ ] `npm run astro-check` and `npm run build` finish without errors, and you have looked at every page whose section files you changed (`astro-check` doesn't check section files or the contact page file)
- [ ] You checked the site at phone width, in dark mode, and in French if you kept it

### Deploy
<Tabs>
  <TabItem label="Netlify">

    <Steps>

    1. Push your project to GitHub, GitLab, or Bitbucket.
    2. In Netlify, choose **Add new site → Import an existing project** and pick the repository.
    3. Netlify reads `netlify.toml`: build command `npm run build`, publish folder `dist`, Node.js 22. Click **Deploy**.

    </Steps>

    The build uses `baseUrl` from `config.toml` unless you add a `SITE_URL` environment variable in the Netlify site settings.

    `netlify.toml` also sets long-term caching for `/_astro/*` and `/images/*`, and sends missing `/fr/...` URLs to the French 404 page. Add a block for each extra language.

    Using Netlify Forms? Set `contactFormProvider = "netlify"` and `contactFormAction = "/"`, enable form detection under **Forms** in the Netlify dashboard, and redeploy. Booking requests use the same provider.

  </TabItem>
  <TabItem label="Vercel">

    <Steps>

    1. Push your project to GitHub, GitLab, or Bitbucket.
    2. In Vercel, choose **Add New → Project** and import the repository. Framework preset: **Astro**.
    3. Vercel reads `vercel.json`, which builds with `sh vercel.sh`. Click **Deploy**.

    </Steps>

    `vercel.sh` sets the site address (`SITE_URL`) for each build, and on Vercel it replaces `baseUrl`:

    - Production builds use the `SITE_URL` environment variable if you set one in the Vercel project settings. Otherwise they use your Vercel production domain. Set `SITE_URL` (for example `https://www.your-domain.com`) when you want one exact address, such as the `www` or the bare domain.
    - Preview builds use `https://staging.<your production domain>`.

    Edit `vercel.sh` if you want something else.

    `vercel.json` also turns on trailing slashes, redirects `/sitemap.xml` to `/sitemap-index.xml`, and serves the `.md` copies (marked `noindex`) and the `llms` files as text.

  </TabItem>
  <TabItem label="Cloudflare Pages">

    **From git:** in the Cloudflare dashboard, open **Workers & Pages → Create → Pages → Connect to Git**, pick the repository, and set the build command to `npm run build` and the output directory to `dist` (the same folder as `pages_build_output_dir` in `wrangler.toml`).

    The included `wrangler.toml` sets `NODE_VERSION = "22.13.1"` under `[vars]`, a version Homio supports. Cloudflare treats this file as the source of truth for the settings in it and shows them read-only in the dashboard, so change them in the file. Set your address with `baseUrl` in `config.toml`.

    **From your computer:** Homio doesn't install Wrangler (Cloudflare's command-line tool) for you. Add it once, change `name` in `wrangler.toml` (shipped as `homio-astro`) to your project name, then log in, build, and deploy:

    ```bash
    npm install -D wrangler
    npx wrangler login
    npm run build
    npm run deploy:cf
    ```

  </TabItem>
  <TabItem label="Other hosts">

    Run `npm run build` and upload the **contents** of `dist/` to your web server or static host (GitHub Pages, S3, a shared host, and so on).

    Ask your host how to serve `.md` files as `text/markdown` and `llms.txt` as `text/plain`, if you keep `[seo.ai] markdownPages` and `llmsTxt` on. Without that, some browsers download those files instead of showing them.

  </TabItem>
</Tabs>

<Aside type="note" title="Keep public/_headers in your repository">
  `public/_headers` tells Netlify and Cloudflare Pages to serve the `llms.txt`
  files and the `.md` page copies as text. Commit it with the rest of the
  theme so a git-connected host sees it.
</Aside>

<Aside type="note">
  When a host builds from your repository, commit `package-lock.json` and keep
  `dist/`, `.astro/`, and `node_modules/` out of git (the included `.gitignore`
  already does).
</Aside>

### After You Launch
<Steps>

1. Open the live site and click through the main pages, on a computer and on a phone, including the mobile menu, the **Call** button, and dark mode.
2. Send a test message through the contact form, and a test booking request through all the steps on `/book/`, and check that both arrive.
3. Try the estimator on `/pricing/` and compare one result with what you would really charge.
4. Type one of your postal codes into the area finder on `/areas/`, and one you don't cover.
5. Submit `https://your-domain/sitemap-index.xml` in [Google Search Console](https://search.google.com/search-console) and [Bing Webmaster Tools](https://www.bing.com/webmasters).
6. Check the homepage and a service page with Google's [Rich Results Test](https://search.google.com/test/rich-results).
7. Share a link in a chat app to check the preview image.

</Steps>

#### Tell Search Engines About Changes With IndexNow
IndexNow tells Bing, Yandex, Seznam, and Naver which pages changed, so they visit them sooner. Bing also feeds ChatGPT search and Copilot. It is off until you set a key:

<Steps>

1. In `config.toml`, set `indexNowKey` under `[seo.ai]` to a key of 8 to 128 letters, numbers, or dashes that you make up:

   ```toml
   [seo.ai]
     indexNowKey = "a1b2c3d4-your-own-key"
   ```

2. Build and deploy. The build publishes the key file at `https://your-domain/<key>.txt`, which the search engines check.

3. After each deploy is live, run from your computer:

   ```bash
   npm run build
   npm run indexnow             # pages changed in the last 7 days
   npm run indexnow -- --all    # every page: use it after your first launch
   ```

   `npm run indexnow -- --dry-run` lists the pages without sending them.

</Steps>

### Updating The Live Site
With a git-connected host, every push to your main branch deploys automatically. Build locally first (`npm run build`) to catch errors before they reach your visitors.