---
id: vanilla/setup
title: Setup & Boot
---

Vanilla TypeScript gets the full runtime: the same store, packs, types, and detection pipeline the React adapter wraps — with no React and no DOM requirement. Works in Node, browsers, workers, anywhere TypeScript runs.

```bash
pnpm add @secundus-studio/stift-core@0.4.0 @secundus-studio/stift-vite-plugin@0.4.0
```

There is no unified re-export here — `@secundus-studio/stift-core@0.4.0` *is* the surface. React apps use [`@secundus-studio/stift-react@0.4.0`](../react/setup.md) instead, which re-exports all of it.

## Create the instance

```ts
import { createStift } from '@secundus-studio/stift-core'
import { createStiftRuntime } from 'virtual:stift/runtime' // emitted by the vite-plugin

const Stift = createStift({
  ...createStiftRuntime(), // locales, detection, state, strategy, source, catalog
  persister, // offline pack storage: OPFS / IndexedDB / Cache API (optional but recommended)
  hooks: { onError: (e) => console.error(e) }, // pack failures, missing keys
})
```

`createStift` is synchronous and side-effect-free. Persisted user state (the locale preference plus `setParam`/`setUnit` overrides) is configured in `stift.config.ts` (`state: { prefix: '__stift_', storage: 'webStorage' }`) and read synchronously at construction; `persister` is where downloaded *pack bytes* live. Without a persister, packs are memory-only (re-fetched each boot) — `Stift.pack.durability` reports `'durable' | 'memory' | 'none'` so UI can gate install affordances honestly. Offline wiring: [Offline & Persistence](../runtime/offline-persistence.md).

## Boot (offline-first)

```ts
const locale = await Stift.detectLocale() // explicit ? persisted ? navigator ? fallback
await Stift.initialize({ locale })        // strategy decides what loads when
```

`initialize` fast-paths returning visitors: installed packs verify + activate from the persister with zero network, and the reactive `packInstalls` status is rehydrated from disk so it reads `installed` from the first notification. Strategy choices: [Loading Strategies](../configuration/loading-strategies.md).

## Reactivity without a framework

`Stift.subscribe((state) => …)` fires on any store mutation (locale, cache, `packInstalls`, `missingKeys`…); read `Stift.state` / `Stift.getLocale()` / `Stift.getDirection()` synchronously anytime. The store underneath is `@tanstack/store` — any reactive binding (including React via `useSyncExternalStore`) can subscribe to the same instance.

## SSR hydration (no SSR boot mode)

The server renders `t()` results directly (`@secundus-studio/stift-core/server`: `resolveLocale` + `FsJsonSource`), then serializes what it rendered:

```ts
// server, after rendering
const state = serializeStiftState(Stift) // { locale, namespaces }
// ? inject into HTML as window.__STIFT_STATE__, set <html lang>
```

```ts
// client ? adopt it: no re-detection, no fallback flash, no re-fetch
const Stift = createStift({ ...runtime, hydrate: window.__STIFT_STATE__ })
await Stift.initialize({ locale: state.locale })
```

There is deliberately no `'ssr'` boot strategy — hydration *is* the SSR story.

## Next

- Your first translated string → [Quickstart](./quickstart.md) and [Translating & Formatting](./translating.md)
- Config schema → [stift.config.ts](../configuration/config-file.md)
