<!-- Source: https://docs.squirro.com/en/latest/technical/neo/dev-guide/build-and-deploy.html -->
# Building and Deploying

> **Warning**
>
> Project Neo is currently in [Technical Preview](../../../a-z/squirro-glossary.md#term-Technical-Preview). Features described in this section may change before general availability.

Bundles are deployed independently from the core Project Neo application. Two deployment models are available:

## Deployment Models

| Model | How it works | Use case |
| --- | --- | --- |
| **Platform deployment** | Bundle uploaded to the Squirro backend. The host loads it per-project from `/v0/workspaces/{domain}/ui_bundles/{name}/dist/`. | Production deployment to a Squirro SaaS or managed instance. |
| **Local bind-mount** | Bundle folder mounted into the Project Neo Docker container at `/app/extensions/`. The host loads it globally. | Local development and testing. |

Use platform deployment for production. The bind-mount model is useful for local testing without uploading to the platform.

## Step 1: Build the Bundle

From your bundle directory:

```bash
neo-ui build
```

The command runs preflight checks before it builds, and stops on a blocking finding such as a hardcoded secret, an access token in a URL, a lint error, or a type error. For the full list of checks, see the [CLI Reference](cli-reference.md#neo-extensions-cli-reference) page. Add `--no-verify` to skip the checks during local iteration. That flag is not available on `neo-ui deploy`, which always runs the checks.

Output in `dist/`:

```text
dist/
├── mf-manifest.json     # Module Federation manifest
├── bundle-info.json     # Deploy metadata (name, version, dashboards), stamped by neo-ui build
├── [hash].js            # Bundle chunk(s), one per dashboard
└── [hash].css           # Styles
```

## Bundle Size

### What the host provides

The host supplies several libraries to bundles at runtime via Module Federation. You do not pay for their size in your bundle:

- `react`, `react-dom`.
- `react-router-dom`.
- `zustand`.
- `@tanstack/react-query`.
- `i18next`, `react-i18next`.

Any other dependency you add to `package.json` is bundled into the output.

### Per-dashboard code splitting

Code splitting happens automatically. The `() => import('./dashboards/MyDashboard')` entry for each dashboard in the manifest produces a separate output chunk. The host fetches that chunk only when the user navigates to the dashboard, not at application load.

For heavy dependencies used in only one dashboard (such as a charting library or a PDF renderer), the same technique applies inside the component itself:

```jsx
import { lazy, Suspense } from 'react';

const HeavyChart = lazy(() => import('./HeavyChart'));

export default function Analytics({ projectId }: { projectId: string }) {
  return (
    <Suspense fallback={<p>Loading chart...</p>}>
      <HeavyChart projectId={projectId} />
    </Suspense>
  );
}
```

That splits `HeavyChart` and its dependencies into a separate chunk that loads on demand.

### Analyzing the bundle

To inspect chunk sizes and the dependency breakdown, run the build with rsbuild’s built-in analyzer flag from inside the project directory:

```bash
BUNDLE_ANALYZE=true npx rsbuild build
```

The command writes a `dist/report.html` file and opens it in the browser. Use it to identify which dependency is responsible for an oversized chunk.

### Size recommendations

No hard limit is enforced by the build tooling, but keep each dashboard chunk under 200 KB parsed (not gzipped) as a practical target. Chunks larger than that add noticeable load time on slower connections when a user first navigates to the dashboard.

If a chunk exceeds that target, use dynamic imports to split the heaviest dependency, or check whether the dependency has a lighter alternative.

## Step 2: Deploy

### Platform Deployment (Recommended)

Use `neo-ui deploy` for a guided, one-command deploy:

```bash
neo-ui deploy
```

The command runs preflight checks (credentials, version compatibility, bundle size, and the content checks of `neo-ui build`), shows an interactive project picker, uploads the bundle, and sets `frontend.ui-bundle` on the selected project, all in one step.

**Flags**

| Flag | Description |
| --- | --- |
| `--no-build` | Skip the build step and deploy the existing `dist/`. |
| `--dry-run` | Run preflight and list available projects without uploading. |
| `--project <id>` | Skip the interactive picker and use a known project ID. Useful for CI. |
| `--force` | Override an incompatible compatibility verdict. |
| `--json` | Emit a machine-readable receipt on stdout, or a structured error object when the deploy fails. |

For a first deploy, run `neo-ui deploy --dry-run` to see the available projects and the compatibility verdict before committing, then run `neo-ui deploy` and select a project from the interactive picker. For subsequent deploys to a known project, skip the picker with `neo-ui deploy --project <project-id>`.

To find the project ID for `--project`, run `neo-ui projects --json` from the bundle directory. In a session without a terminal, such as a CI job, `neo-ui deploy` without `--project` uploads the bundle but does not activate it, and reports that `frontend.ui-bundle` has to be set manually.

For a CI pipeline, deploy to a known project and capture the machine-readable receipt:

```bash
neo-ui deploy --project $PROJECT_ID --json | jq .
```

> **Note**
>
> If `neo-ui deploy` is unavailable, use the `squirro_asset` tool as a fallback. That tool is part of the Squirro Toolbox. For installation instructions, see the [squirro_asset CLI Reference](../../api/toolbox/asset-cli.md#toolbox-asset-cli) page.
>
>
>
> Run the following command, where `<folder>` is a directory named after the bundle that contains a `dist/` subdirectory:
>
>
>
> ```bash
> squirro_asset ui_bundle upload \
>   --cluster https://your-instance.example.com/ \
>   --token <your-refresh-token> \
>   --folder <folder>/
> ```
>
>
>
> The `--token` value is passed as a command-line argument, which means it appears in your shell history and in process listings. After running the command, clear the relevant history entry or use your shell’s history-ignore facility for sensitive commands.
>
>
>
> Then set the `frontend.ui-bundle` project configuration to the bundle name. That setting is available under Setup Space → Settings → Project Configuration:
>
>
>
> ```text
> frontend.ui-bundle = <bundle-name>
> ```

### How Loading Works

When a user navigates to a project, the host:

1. Fetches the project configuration and reads the `frontend.ui-bundle` value.
2. Loads `mf-manifest.json` from `/v0/workspaces/<domain>/ui_bundles/<bundle-name>/dist/mf-manifest.json`.
3. Registers the Module Federation remote and loads the bundle manifest.
4. Adds the bundle dashboards to the sidebar navigation and creates dynamic routes.

The bundle is loaded once per session and cached across project revisits within the same browser session.

### Local Bind-Mount

To test a production build locally without uploading to the platform, mount the `dist/` folder into the Project Neo Docker container:

```yaml
# docker-compose.local.yml
volumes:
  - /path/to/your/bundle/dist:/app/extensions:ro
```

The host detects the `/extensions/mf-manifest.json` path and loads the bundle globally for all projects. No `frontend.ui-bundle` project configuration is needed in this mode.

> **Note**
>
> The bind-mount model is for local development and testing only. Use platform deployment for production.

## Step 3: Verify

After deploying, open the Project Neo application in the browser and hard-refresh (`Cmd+Shift+R` on macOS, `Ctrl+Shift+R` on Windows and Linux). Your bundle dashboards should appear in the sidebar.

To verify the manifest is reachable after a platform deployment:

```text
https://your-instance.example.com/v0/workspaces/<domain>/ui_bundles/<bundle-name>/dist/mf-manifest.json
```

To verify the manifest is reachable after a local bind-mount:

```text
http://localhost:5555/extensions/mf-manifest.json
```

Both URLs should return JSON. If either returns HTML or a 404, the bundle did not reach the correct path.

## Updating a Bundle

1. Make changes in `src/`.
2. Run `neo-ui build`.
3. Run `neo-ui deploy` again to upload the new build, overwriting the previous one.
4. Users see the update on their next page load. No server restart is needed.

Chunk filenames include content hashes, so new and old chunks never conflict. Only `mf-manifest.json` is overwritten in place.

## Version Compatibility

Each bundle build records the `platformVersion` it was compiled against, which is the `@squirro/neo-core` version in your project. That version follows the Squirro platform version, so a bundle built against `3.17.1` targets the 3.17.1 platform. `neo-ui deploy` checks the recorded version against the running host version and gates the deploy on the result:

| Verdict | Meaning | Deploy behavior |
| --- | --- | --- |
| `compatible` | Exact version match. | Proceeds. |
| `degraded` | Same major version, different minor, patch, or pre-release. | Warns and proceeds. |
| `incompatible` | Different major version. | Blocked. Fix before deploying. |
| `unknown` | Instance predates version reporting. | Warns and proceeds. |

If the deploy is blocked as `incompatible`, upgrade the bundle to match the host, then deploy again:

```bash
neo-ui upgrade
neo-ui deploy
```

To override the gate and deploy anyway, use `--force`:

```bash
neo-ui deploy --force
```

To check the verdict in advance without uploading, run `neo-ui deploy --dry-run`.

## Multiple Bundles

The host supports one bundle per project. If your use case requires multiple bundles for a single project, visit the [Squirro Support website](https://go.squirro.com/support) and submit a technical support request.
