# Browser builds (/docs/browser-builds)

A browser bundle is public, and it can't read `.env` files or environment variables when it runs. So when a config file is
imported from client code, tyfigo:

1. Inlines the **base and environment files** only, merged at build time. Local files (`app.local.config.*`) are left out.
2. **Fails the build** on any `${…}` placeholder in those files, so a secret can't be inlined by accident.
3. **Fails the build** when client code imports `loadEnv` or another [Node.js-only export](#nodejs-only-exports).

```txt
Invalid config /srv/app/config/bad.config.yaml
/srv/app/config/bad.config.yaml:1:6 - 'url' uses an environment placeholder, which browser builds cannot read (browser-placeholder)
```

A literal `$${` is allowed.

## Which builds are browser builds [#which-builds-are-browser-builds]

| Tool                     | Browser target                             |
| ------------------------ | ------------------------------------------ |
| Vite                     | Client environment (not SSR)               |
| Next.js                  | Client components and Edge code            |
| webpack, Rspack, Rsbuild | A `web` target                             |
| esbuild, tsup            | Any `platform` other than `node`           |
| Rolldown, tsdown         | Any `platform` other than `node`           |
| Rollup                   | Always, unless you pass `target: 'server'` |
| `Bun.build`              | `target: 'browser'`                        |

Every plugin takes `target: 'browser' | 'server'` to override the detected one (except Next.js, which decides per layer).

## Split client and server config [#split-client-and-server-config]

Keep values the browser may see in one file and server-only values in another. From the
[rspack example](https://github.com/imranbarbhuiya/tyfigo/tree/main/examples/rspack):

```yaml title="config/app.config.yaml"
title: tyfigo + Rspack
analytics:
  enabled: false
  sampleRate: 1
links:
  docs: http://localhost:3000/docs
```

```yaml title="config/server.config.yaml"
port: ${PORT:-3000}
cache:
  url: ${CACHE_URL}
  maxEntries: 100
```

`src/client.ts` imports `app.config.yaml`, and `src/server.ts` imports `server.config.yaml`. The client bundle contains the
production values of `app.config.yaml` and nothing from `.env`.

## Server bundles [#server-bundles]

Build once and deploy the same bundle to every environment. A server bundle inlines the text of every layer file that exists
when it builds: the base file, every environment file (`app.<env>.config.*`), and the local files (`app.local.config.*`,
`app.<env>.local.config.*`). When the server starts, it picks the layers by `NODE_ENV`:

```txt
config/app.config.yaml              always
config/app.staging.config.yaml      NODE_ENV=staging
config/app.production.config.yaml   NODE_ENV=production
app.local.config.yaml               always
```

So a bundle promoted from staging to production uses `app.production.config.yaml` there. With no environment file for the
`NODE_ENV` it runs under, it uses the base and local files only. The bundler's mode and `NODE_ENV` at build time don't matter.

Pass `environment` to the plugin to fix it instead. The bundle then inlines only that environment's files and ignores
`NODE_ENV` when it starts:

```ts
tyfigo({ environment: 'production' });
```

When the server starts, the bundle also resolves placeholders from the environment of the process and the `.env` files in its
working directory: `.env`, `.env.local`, `.env.<env>` and `.env.<env>.local`. A missing variable, or one that doesn't read as
its placeholder's type, is not a build error; it fails when the server starts. Paths of the build machine are not baked in.

Every environment file is checked when the bundle builds, so a bad `app.staging.config.yaml` fails a build you meant for
production. In watch mode, editing any of the inlined files rebuilds the bundle. A new environment file is picked up by the
next build. Rollup and webpack also rebuild when the file for the build's `NODE_ENV` is created.

## Node.js-only exports [#nodejs-only-exports]

`loadEnv`, `loadEnvAsync` and `layerEnv` read `.env` files and environment variables, and `configProviderFromFile` and
`layerConfigProviderFromFile` read config files from disk. Importing any of them from client code fails the build:

```txt
/srv/app/src/client.ts:1:1 - loadEnv from 'tyfigo' reads files and environment variables with Node.js, so it cannot run in a browser bundle. Call it in server code, or set the tyfigo plugin's target option to 'server' if this bundle runs on a server.
```

Next.js with Turbopack reports it as an export that `tyfigo` doesn't have. `loadConfig` and `configProvider` work in the browser. Bundlers that resolve the `browser` export condition get entries of
`tyfigo` and `tyfigo/effect` without the Node.js-only exports, so a client bundle carries no Node.js code.
