> ## Documentation Index
> Fetch the complete documentation index at: https://constanza101-borrissol-28.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Netlify deployment: Borrissol build config and cost control

> How Borrissol deploys to Netlify: build configuration, Node version pinning, bot-trap redirects, credit model, and cost control best practices.

Borrissol deploys to Netlify via git push auto-deploy. Every merge to `main` triggers a production build; the result is published at [borrissol.com](https://borrissol.com). The site is a hybrid: all public-facing pages are statically prerendered at build time (zero Netlify Function invocations for ordinary visitors), while the Keystatic admin routes at `/keystatic` use SSR via a Netlify Function. This architecture keeps operating costs low while still supporting a dynamic CMS.

***

## Build configuration

The entire Netlify build is controlled by `netlify.toml` at the repository root.

```toml netlify.toml theme={null}
[build]
  command = "npm run build"
  publish = "dist"

[build.environment]
  NODE_VERSION = "22.12.0"
```

| Setting        | Value           | Reason                                                                                                                                             |
| -------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `command`      | `npm run build` | Runs `astro build`, which prebuilds all static pages and bundles the SSR functions.                                                                |
| `publish`      | `dist`          | The directory Astro writes its output to; Netlify deploys its contents.                                                                            |
| `NODE_VERSION` | `22.12.0`       | Pinned to match the `engines` field in `package.json` (`>=22.12.0`). Astro 6 requires Node 22+. Netlify defaults to an older LTS without this pin. |

### `@astrojs/netlify` adapter

The `netlify()` adapter (package `@astrojs/netlify ^7.0.12`) is declared in `astro.config.mjs`. It handles two responsibilities:

1. **Static output** — All pages that do not require server-side rendering are emitted as pre-built HTML/CSS/JS into `dist/`. These are served directly from Netlify's global CDN with no function invocation.
2. **SSR functions** — The Keystatic admin routes (`/keystatic/*`) require a live server to authenticate requests and call the GitHub API. The adapter wraps these in a single Netlify Function automatically.

<Note>
  Public visitors never invoke an SSR function. The homepage, workshop pages, gallery, blog listing, and individual blog posts are all statically prerendered. Only Belén (the workshop owner) accessing `/keystatic` in production triggers function compute.
</Note>

***

## Bot-trap redirects

`netlify.toml` contains a block of `[[redirects]]` rules that return HTTP 404 at the **Netlify edge layer** for paths that only automated scanners and bots probe. Because these rules fire before any Astro code runs, a matched request never reaches the SSR function — saving both compute time and credits.

```toml netlify.toml theme={null}
# WordPress install/admin/login probes
[[redirects]]
  from = "/wp-admin/*"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/wp-login.php"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/wp-content/*"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/wp-includes/*"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/wp-config.php"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/xmlrpc.php"
  to = "/_blocked"
  status = 404
  force = true

# Stray PHP install/info probes
[[redirects]]
  from = "/install.php"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/phpinfo.php"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/admin.php"
  to = "/_blocked"
  status = 404
  force = true

# Common admin-tool path probes
[[redirects]]
  from = "/phpmyadmin/*"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/administrator/*"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/typo3/*"
  to = "/_blocked"
  status = 404
  force = true

# Source-control / secrets / config exposures
[[redirects]]
  from = "/.env"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/.git/*"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/config.php"
  to = "/_blocked"
  status = 404
  force = true

# Common backup/dump probes
[[redirects]]
  from = "/backup/*"
  to = "/_blocked"
  status = 404
  force = true

[[redirects]]
  from = "/backups/*"
  to = "/_blocked"
  status = 404
  force = true
```

### Why these rules matter

Without them, every WordPress scanner probe that hits the site would invoke the Astro SSR function (1–3 seconds of compute per request) before returning a 404. On a shared hosting plan with a credit budget, continuous bot traffic can exhaust the monthly allowance within hours of a traffic spike. The edge-layer 404 approach costs essentially nothing.

<Warning>
  Do not remove these bot-trap rules without first migrating to a platform with native bot protection (such as Cloudflare Pages, which includes Bot Fight Mode). Simply deleting the rules and relying on Astro's 404 page would route all probe traffic through the SSR function.
</Warning>

***

## Netlify credit model

Netlify's paid plans are denominated in "credits" that cover both build compute and bandwidth. The table below shows the three relevant tiers.

| Plan                            | Credits/month | \~Deploys/month | \~Bandwidth |
| ------------------------------- | ------------- | --------------- | ----------- |
| Free                            | 300           | \~20            | \~15 GB     |
| **Personal (\$9/mo) ← current** | 1,000         | \~65            | \~50 GB     |
| Pro (\$20/mo)                   | 3,000         | \~200           | \~150 GB    |

**Credit consumption rules of thumb:**

* 1 production deploy ≈ **15 credits** (build time + CDN invalidation)
* 1 GB of bandwidth served ≈ **20 credits**
* Netlify Function invocations (SSR) consume credits from the same pool, charged by compute time

At the Personal plan's 1,000 credits/month, you have roughly **65 production deploys** before additional credits need to be purchased — or roughly 50 GB of traffic headroom if all credits go to bandwidth.

***

## Cost control rules

Following these practices keeps the site comfortably within the Personal plan budget.

**Batch changes.** Never push one commit per small fix. Accumulate related changes and push them together. One push = one deploy = \~15 credits.

**Use `[skip ci]`** in the commit message for documentation, comment, or README-only changes that do not affect the built site. Netlify skips the build entirely when it sees `[skip ci]`.

**Keystatic `keystatic/*` branches do not trigger deploy previews.** Deploy previews are disabled for these branches to avoid burning 15 credits on every draft save by the content author.

**Public visitors load static pages.** No SSR function is invoked by ordinary site traffic. Only the `/keystatic` admin routes consume function compute, and only when the site owner is actively using the CMS.

**Monitor the Netlify dashboard.** Check **Team → Usage** regularly. If bandwidth spikes (e.g. a blog post goes viral), investigate the source before the monthly credit pool is exhausted.

<Tip>
  If the site grows to the point where 65 deploys/month is regularly tight, upgrading to the Pro plan (\$20/mo, \~200 deploys) is more cost-effective than buying credit top-ups.
</Tip>

***

## Deployment workflow

<Steps>
  <Step title="Verify the build locally">
    Before pushing, run a full production build locally to catch any errors:

    ```bash theme={null}
    npm run build
    ```

    Astro will output the built site to `dist/`. Optionally preview it with:

    ```bash theme={null}
    npm run preview
    ```
  </Step>

  <Step title="Push to main">
    ```bash theme={null}
    git push origin main
    ```

    Netlify detects the push, clones the repository, runs `npm run build`, and deploys the `dist/` directory to the CDN. The process typically takes 60–120 seconds.
  </Step>

  <Step title="Check deploy status">
    Open the [Netlify dashboard](https://app.netlify.com) and navigate to the **Deploys** tab for the Borrissol site. Confirm the deploy status shows **Published**. If it shows **Failed**, click the deploy entry to read the build log.
  </Step>
</Steps>

***

## Deploy previews

Deploy previews are currently **disabled** for all branches to conserve credits. If you need to preview a feature branch before merging:

<Steps>
  <Step title="Enable temporarily">
    In the Netlify dashboard go to **Site configuration → Build & deploy → Deploy contexts**. Set **Deploy previews** to **Any pull request**.
  </Step>

  <Step title="Open the PR">
    Push your branch and open a Pull Request on GitHub. Netlify will post a preview URL in the PR comments.
  </Step>

  <Step title="Re-disable after review">
    Once the preview has been reviewed and the PR merged, return to **Deploy contexts** and set **Deploy previews** back to **None** to stop future branches from triggering builds.
  </Step>
</Steps>

<Note>
  Deploy previews for `keystatic/*` branches should **never** be re-enabled. Keystatic creates one branch per entry save; enabling previews for those branches would cost \~15 credits per auto-save.
</Note>
