<!-- https://svgrid.com/docs/help/production/ - SvGrid documentation as markdown. Index of every page: https://svgrid.com/llms.txt -->

# Production deployment

The checklist that turns "it works on my laptop" into "it ships". One
page per concern; each concern is one paragraph + the code that
matters.

![Six production concerns turn a working grid into a shippable one: server-side data, virtualization, accessibility, SSR and hydration, CSP, and TypeScript.](https://svgrid.com/docs-media/grid-production.svg)

> Live demo: Admin template - https://svgrid.com/demos/22-admin-template/

## 1. Pin your versions

The examples on this page run against these rows:

```svelte
<script lang="ts">
  import { SvGrid, type GridColumns } from '@svgrid/grid'

  type Person = {
    id: number
    name: string
    email: string
    department: string
    age: number
    salary: number
    city: string
    startDate: string
    active: boolean
  }

  const people: Person[] = [
    { id: 1, name: 'Ada Lovelace',   email: 'ada@example.com',   department: 'Engineering', age: 36, salary: 142000, city: 'London',   startDate: '2021-03-01', active: true },
    { id: 2, name: 'Grace Hopper',   email: 'grace@example.com', department: 'Engineering', age: 45, salary: 168000, city: 'New York', startDate: '2019-07-15', active: true },
    { id: 3, name: 'Linus Torvalds', email: 'linus@example.com', department: 'Platform',    age: 54, salary: 155000, city: 'Portland', startDate: '2020-01-20', active: false },
    { id: 4, name: 'Radia Perlman',  email: 'radia@example.com', department: 'Networking',  age: 49, salary: 161000, city: 'Seattle',  startDate: '2022-09-05', active: true },
    { id: 5, name: 'Barbara Liskov', email: 'barbara@example.com', department: 'Platform',  age: 52, salary: 172000, city: 'Boston',   startDate: '2018-11-11', active: true },
  ]

  const columns: GridColumns<Person> = [
    { field: 'name',       header: 'Name',       width: 200 },
    { field: 'department', header: 'Department', width: 150 },
    { field: 'city',       header: 'City',       width: 140 },
    { field: 'age',        header: 'Age',        width: 90 },
    { field: 'salary',     header: 'Salary',     width: 130, format: { type: 'currency', currency: 'USD' } },
  ]
</script>
```

```json
{
  "dependencies": {
    "@svgrid/grid": "1.0.0",
    "@svgrid/enterprise":       "1.0.0"
  }
}
```

Pre-1.0, prefer exact pins (no `^`, no `~`). The
[changelog](https://svgrid.com/docs/changelog/) annotates breaking changes; the
[API stability page](https://svgrid.com/docs/help/api-stability/) names which exports are
under the semver promise.

## 2. Bundle size: what actually ships

Measured gzipped, with Svelte excluded as a peer dependency:

| What you import                                       | Gzipped | Minified |
| ----------------------------------------------------- | ------- | -------- |
| Headless core (`createGrid` + `createCoreRowModel`)   | ~2 KB   | ~7 KB    |
| Full `<SvGrid>` render component (everything)         | ~77 KB  | ~340 KB  |

Add ~9 KB gzipped for the render component's CSS. Charts, date/time
editors, menus, and export split into `import()` chunks (~64 KB total)
that load on demand. Re-measure with `pnpm size`.

The `<SvGrid>` component is batteries-included: virtualization, Excel-style
filters, inline editing, grouping, tree, master/detail, and accessibility are
all in that one import. For a smaller footprint, use the headless core and
render your own markup, registering only the features you need.

Enterprise adds per feature you import:

| Enterprise module          | Approx KB | Peer deps                       |
| ------------------- | --------- | ------------------------------- |
| `exportGrid` (csv/tsv/html) | ~6 KB    | -                          |
| + xlsx               | ~6 KB    | `jszip` (loaded on first xlsx call) |
| + pdf                | ~9 KB    | `pdfmake` (loaded on first pdf call) |
| `importData`         | ~7 KB    | `jszip` (xlsx only)             |
| AI helpers           | ~3 KB    | -. You bring your provider.     |
| `createPivotModel`   | ~4 KB    | -                               |

Use the **subpath imports** to avoid pulling features you don't use:

```ts
import { exportGrid }      from '@svgrid/enterprise/export'    // export only
import { createPivotModel } from '@svgrid/enterprise/pivot'    // pivot only
```

## 3. Peer dependencies

| Peer dep   | When you need it                                                   | Install                             |
| ---------- | ------------------------------------------------------------------ | ----------------------------------- |
| `svelte`   | Always. SvGrid renders against Svelte 5.                           | `pnpm add svelte`                   |
| `jszip`    | xlsx export OR xlsx import.                                        | `pnpm add jszip`                    |
| `pdfmake`  | PDF export.                                                        | `pnpm add pdfmake`                  |

Both `jszip` and `pdfmake` are dynamic imports - the bundle splits and
loads them on the first call. Nothing ships in your initial chunk until
the user actually clicks "Export to xlsx".

## 4. Lazy-load Enterprise at route boundaries

If only one route in your app needs export, gate `installEnterprise` behind a
dynamic import so the rest of the app doesn't ship the Enterprise bundle:

```svelte
<script lang="ts">
  import type { SvGridApi } from '@svgrid/grid'
  import type { EnterpriseGridApi } from '@svgrid/enterprise'

  let api = $state<SvGridApi<typeof features, Order> | null>(null)
  let pro = $state<EnterpriseGridApi<typeof features, Order> | null>(null)

  async function enablePro() {
    if (!api) return
    const { installEnterprise, setLicenseKey } = await import('@svgrid/enterprise')
    setLicenseKey(import.meta.env.VITE_SVPRO_KEY)
    pro = installEnterprise(api)
  }
</script>

<SvGrid {...} onApiReady={(next) => (api = next)} />

<button onclick={enablePro}>Enable export</button>
{#if pro}
  <button onclick={() => pro?.exportData({ format: 'xlsx' })}>⬇ XLSX</button>
{/if}
```

## 5. License the Enterprise pack

```ts
// main.ts (or +layout.svelte for SvelteKit)
import { setLicenseKey } from '@svgrid/enterprise'

if (import.meta.env.VITE_SVPRO_KEY) {
  setLicenseKey(import.meta.env.VITE_SVPRO_KEY)
}
```

Enterprise comes with a **30-day evaluation period**. Loaded without a
key it works, and displays a small watermark and a console message about
the Enterprise package. Set the key once at app startup; both disappear.

Don't commit the key to source control. Inject via env (Vite reads
`VITE_*` variables at build time; SvelteKit reads `$env/static/public`).

For per-tenant deployments where each tenant has their own key, set
the key inside the consumer's bootstrap, never inside the library
package.

## 6. CSP-safe deployment

The recommended `Content-Security-Policy` header:

```
Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  font-src 'self' data:;
  connect-src 'self';
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
```

No `'unsafe-eval'`, no `'unsafe-inline'` on `script-src`. SvGrid
Community + Enterprise run clean under this policy. [Demo 16](https://svgrid.com/demos/16-csp-compliant/)
includes a runtime self-check.

If you ship in an iframe (embedded analytics, dashboards), add
`frame-ancestors` to the host's CSP to allow the embed.

## 7. SSR

For SvelteKit:

```ts
// +page.server.ts
export async function load() {
  const rows = await db.query('select * from people limit 100')
  return { rows }
}
```

```svelte
<!-- +page.svelte -->
<script lang="ts">
  import { SvGrid, tableFeatures, rowSortingFeature } from '@svgrid/grid'
  let { data } = $props()
  const features = tableFeatures({ rowSortingFeature })
</script>

<SvGrid data={data.rows} columns={columns} features={features} />
```

The first paint contains the data in a real `<table>` (good for SEO +
LCP). Hydration only attaches event listeners. See
[demo 19](https://svgrid.com/demos/19-ssr/) for a sandboxed
JS-disabled iframe that proves the markup is meaningful pre-hydration.

## 8. Performance budgets

Targets that have held up in production:

| Surface                            | Target               | What you do if you miss it                                       |
| ---------------------------------- | -------------------- | ---------------------------------------------------------------- |
| Time to first row visible          | < 200 ms             | Lazy-load Enterprise. Defer non-critical columns. Smaller initial page. |
| Scroll FPS (10k rows, virtualized) | 60 FPS               | Cap `overscan`. Avoid `cell` render functions that allocate per render. |
| Sort over 100k rows                | < 60 ms              | Set `editorType` on numeric / date columns so `sortFns.number` / `sortFns.date` get used instead of `sortFns.auto`. |
| Filter input → re-render           | < 30 ms              | Debounce server-side filters; the local filter UI is already ≤ 16 ms for 10k rows. |
| Export 10k rows to xlsx            | < 1 s                | Don't include columns you're going to hide. Use `columns: [...]` to project. |

The [benchmarks page](https://svgrid.com/docs/help/benchmarks/) has the reproducible numbers.

## 9. Error boundaries

The render component throws on truly broken state (e.g. a `field` that
doesn't exist on any row). Wrap in a Svelte error boundary or guard
with `if (rows.length === 0)` for empty data. The grid's `emptyMessage`
prop covers the empty case without crashing.

```svelte
<svelte:boundary>
  <SvGrid {data} {columns} {features} />

  {#snippet failed(error, reset)}
    <div class="error">Grid failed: {error.message}</div>
    <button onclick={reset}>Retry</button>
  {/snippet}
</svelte:boundary>
```

## 10. Monitoring + observability

The grid emits everything you'd want to observe via callbacks:
`onSortingChange`, `onFiltersChange`, `onRowSelectionChange`,
`onCellValueChange`. Wire them into your analytics / logging:

```ts
function track(event: string, payload: object) {
  // sentry, posthog, your own beacon - pick one
}

<SvGrid
  ...
  onSortingChange={(s)     => track('grid.sort',   { clauses: s })}
  onFiltersChange={(f)     => track('grid.filter', { columns: f.columns.length })}
  onCellValueChange={(e)   => track('grid.edit',   { column: e.columnId })}
/>
```

## A production-shaped grid

Everything a real screen tends to turn on at once: virtualization for the row
count, a stable `getRowId` so selection survives a refetch, and the footer
totals people ask for on day two.

```svelte
<script lang="ts">
  import { SvGrid, type GridColumns } from '@svgrid/grid'

  type Order = { id: string; customer: string; status: string; total: number }

  // Enough rows that virtualization is doing real work.
  const orders: Order[] = Array.from({ length: 500 }, (_, i) => ({
    id: 'A-' + String(1000 + i),
    customer: ['Northwind', 'Contoso', 'Fabrikam', 'Adventure'][i % 4]!,
    status: ['pending', 'shipped', 'delivered'][i % 3]!,
    total: 80 + ((i * 37) % 900),
  }))

  const columns: GridColumns<Order> = [
    { field: 'id',       header: 'Ref',      width: 110 },
    { field: 'customer', header: 'Customer', width: 170 },
    { field: 'status',   header: 'Status',   width: 130 },
    { field: 'total',    header: 'Total',    width: 130, summary: 'sum',
      format: { type: 'currency', currency: 'USD' } },
  ]
</script>

<SvGrid
  data={orders}
  {columns}
  getRowId={(r) => r.id}
  sortable
  filterable
  summary
  pageable
  pageSize={25}
/>
```

## See also

- [Why headless?](https://svgrid.com/docs/why-headless/) - the architectural decision
  behind Community + Enterprise.
- [API stability](https://svgrid.com/docs/help/api-stability/) - the semver promise and what
  it covers.
- [Security](https://svgrid.com/docs/help/security/) - peer-dep table, SBOM, vulnerability
  handling, data residency.
- [Browser support](https://svgrid.com/docs/help/browser-support/) - tested matrix, mobile,
  build tools.

---

SvGrid is the Svelte 5 data grid (`npm install @svgrid/grid`, MIT). This page: https://svgrid.com/docs/help/production/ . All docs: https://svgrid.com/llms.txt
