# Migrate from Netlify

Checked against the code on 2026-10-30 (version 0.1.0).

SeeDropHost does not run your build command. If your site needs a build, build it as you do today; then point `sdh deploy` at the repository root (it reads `netlify.toml` and publishes the `publish` folder) or straight at the built folder.

## What is read automatically

| In your project | What SeeDropHost does |
|---|---|
| `netlify.toml` or `seedrop.toml` | `[build] publish`: only that folder goes live. `[[redirects]]` (from, to, status, force, Language/Country conditions, including `from = "https://www.example.com/*"`) and `[[headers]]` (`for` + `[headers.values]`). `seedrop.toml` wins if both exist. The file itself is never served. |
| `_redirects` | read as is: 200/301/302/303/307/308/404/410, `*` and `:splat`, `:placeholders`, `!` to force, `Language=` and `Country=`, proxy to public addresses. [Details](/docs/redirects-headers/) |
| `_headers` | read as is: path blocks with wildcards, `Name: value` lines (`Set-Cookie` is dropped) |
| `<form data-netlify="true" name="…">` | detected on deploy; submissions go to the lead inbox. `netlify-honeypot="field"` is respected; `form-name` is added for you. [Details](/docs/forms/) |
| Functions in `[functions] directory` (default `netlify/functions`) | carried with the deploy and run only if the server has `SDH_FUNCTIONS=1` — see below |
| `404.html` | served with status 404 for unknown paths |
| Clean URLs | `/about` serves `about.html` or `about/index.html`; the other form answers 301 to the one that exists |

The same layout is applied to a `.zip` of the repository root and to a folder dropped in the dashboard.

## Functions: opt-in, for a single owner

Supported formats, one file per function (`.js`, `.mjs` or `.cjs`):

```js
// classic
exports.handler = async (event, context) => ({ statusCode: 200, body: "ok" });

// modern
export default async (req) => new Response("ok");
```

They answer at `/.netlify/functions/<name>` and `/.sdh/functions/<name>`, and through 200 rules such as `/api/*  /.netlify/functions/:splat  200`. Limits: 10 seconds, 128 MB of memory, 6 MB request and 6 MB response. `process.env` holds only the site's variables with the `functions` scope.

> **Warning:** functions are **off by default**. Turn them on (`SDH_FUNCTIONS=1`) only if you own every site on the server. A function runs in a worker of the same process with time and memory limits, but that is **not isolation**: it can read the database, the files of every site and the server credentials. Whoever publishes a function has full access to the server. With functions off, those paths answer 404.

## What is not read yet

- No build from Git and no deploy on push. `[build] command`, plugins and `[context.*]` are not run. Build locally or in CI, then run `sdh deploy`.
- Edge functions, scheduled functions and background functions.
- Functions that `require` neighbouring files or `node_modules` (each function is one file), and functions in TypeScript (reported as a warning).
- In `[[redirects]]`: `query`, `signed`, `headers` and the `Role` condition are reported as warnings and ignored. `_redirects` conditions other than `Language` and `Country` never match.
- Forms: no email notifications, no file uploads, no reCAPTCHA. Spam is filtered by honeypot, link count and rate limit.
- No global CDN of its own: put Cloudflare in front ([self-host guide](/docs/self-host/)).

Every one of these shows up in the deploy report, so nothing is skipped silently.

## Step by step

1. Build the site as you do today, for example `npm run build`.
2. Create the site and upload it as a **draft**. A draft gets its own preview address and does not go live; your domain still points to Netlify, so visitors see no change.

```bash
npx sdh create my-site
npx sdh deploy ~/my-repo --site my-site --draft
```

3. Read the report the command prints: the config file it read, the published folder, functions found, SEO/GEO score, forms found and any rule that was not understood.
4. Compare the live Netlify site with the draft preview, page by page, including the old addresses listed in your `_redirects` and `netlify.toml`:

```bash
npx sdh comparar https://www.your-site.com https://<deploy>--my-site.your-server.example --projeto ~/my-repo --json report.json
```

It blocks (exit code 1) when status, redirects, canonical, noindex, hreflang, forms or a new `robots.txt` Disallow over existing pages differ, and warns about title, description, h1, JSON-LD, text and URLs missing from the sitemap. A preview address (`<deploy>--<site>.`) is recognised automatically: the `noindex` every preview carries does not count, but a `noindex` your site declares itself still blocks. Use `--previa` to force this for another address. If the old site's home redirects to another host (bare domain ↔ `www`), the comparison follows it once and says so.

5. Fix, deploy again, compare again until nothing blocks. Then publish: `npx sdh publish my-site <deploy-id>`.
6. Connect the domain: `npx sdh domain:add my-site www.your-site.com`. Create the TXT record it prints (proof that the domain is yours) and the CNAME (or A records for the bare domain). Check with `npx sdh domain:check www.your-site.com`. The domain is served only after the TXT is verified.
7. Keep the Netlify site until DNS has switched and the leads arrive in the new inbox (`npx sdh leads my-site`).

## Undo

Point DNS back to Netlify. On SeeDropHost every deploy stays available; `sdh publish` with an earlier id rolls back instantly.
