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.
1. Pin your versions
{
"dependencies": {
"sv-grid-community": "1.0.0",
"sv-grid-pro": "1.0.0"
}
}
Pre-1.0, prefer exact pins (no ^, no ~). The
changelog annotates breaking changes; the
API stability page 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 (createSvGrid + createCoreRowModel) |
~7.5 KB | ~35 KB |
Full <SvGrid> render component (everything) |
~42 KB | ~189 KB |
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.
Pro adds per feature you import:
| Pro module | Approx KB | Peer deps |
|---|---|---|
exportGrid (csv/tsv/html) |
~6 KB | - |
| + xlsx | ~6 KB | jszip (loaded on first xlsx call) |
| ~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:
import { exportGrid } from 'sv-grid-pro/export' // export only
import { createPivotModel } from 'sv-grid-pro/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 Pro at route boundaries
If only one route in your app needs export, gate installPro behind a
dynamic import so the rest of the app doesn't ship the Pro bundle:
<script lang="ts">
import type { SvGridApi } from 'sv-grid-community'
import type { ProGridApi } from 'sv-grid-pro'
let api = $state<SvGridApi<typeof features, Order> | null>(null)
let pro = $state<ProGridApi<typeof features, Order> | null>(null)
async function enablePro() {
if (!api) return
const { installPro, setLicenseKey } = await import('sv-grid-pro')
setLicenseKey(import.meta.env.VITE_SVPRO_KEY)
pro = installPro(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 Pro pack
// main.ts (or +layout.svelte for SvelteKit)
import { setLicenseKey } from 'sv-grid-pro'
if (import.meta.env.VITE_SVPRO_KEY) {
setLicenseKey(import.meta.env.VITE_SVPRO_KEY)
}
Pro is soft-gated - it works unlicensed, but renders a small watermark + a one-time console nudge. 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 + Pro run clean under this policy. Demo 16
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:
// +page.server.ts
export async function load() {
const rows = await db.query('select * from people limit 100')
return { rows }
}
<!-- +page.svelte -->
<script lang="ts">
import { SvGrid, tableFeatures, rowSortingFeature } from 'sv-grid-community'
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 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 Pro. 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 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: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:
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 })}
/>
See also
- Why headless? - the architectural decision behind Community + Pro.
- API stability - the semver promise and what it covers.
- Security - peer-dep table, SBOM, vulnerability handling, data residency.
- Browser support - tested matrix, mobile, build tools.