Tech Stack
Standard technology choices for Activatus projects.
๐ Standard Stackโ
- Framework: Next.js 16 (App Router)
- Language: TypeScript 7
- Package Manager: pnpm (monorepo: workspaces)
- Styling: Tailwind CSS
- Node Version:
^22.11 || ^24 || >=26 - Build Tool: Turbopack (Next.js built-in)
TypeScript 7โ
TypeScript 7 is the compiler rewritten in Go. It is a drop-in replacement for the JavaScript implementation for compiling and type-checking, but two things change for tooling:
- There is no
tsserver. The compiler is its own language server, started withtsc --lsp --stdio. Editors and tools that drove TypeScript throughtypescript-language-serverhave nothing to drive; they talk totscdirectly instead. - It answers diagnostics on request. TypeScript 7 supports LSP pull
diagnostics, so a client that only waits for a
publishDiagnosticspush can sit there believing a file is clean.
Parameterise a library's genericsโ
A generic whose default is a permissive index signature returns any at every use, and
the code reads as though it were typed. Storyblok's story type is the one to watch:
// storyblok-js-client
interface ISbStoryData<
Content = ISbComponentType<string> & { [index: string]: any },
> {
content: Content;
// โฆ
}
Left bare, ISbStoryData makes story.content that any-indexed fallback. Every field
access resolves, nothing is checked, and a renamed block field fails in the browser
rather than in CI. Declare the block and pass it:
export interface HeroBlokType extends ISbComponentType<"hero"> {
headline: string;
subhead?: string;
callToAction?: CallToActionBlokType[];
}
// story.content.headline is string; story.content.headlien is a compile error.
const story: ISbStoryData<HeroBlokType> = await getStory();
The parameter flows, so the surrounding types carry it too โ ISbResult's
data.stories becomes ISbStoryData<HeroBlokType>[]. Where a story can hold one of several
blocks, make Content a discriminated union on component and switch on it.
Extend the published type rather than restating its fields: ISbComponentType<"hero">
already contributes _uid, component and _editable, and a hand-written copy drifts
the moment the library changes.
Read every library generic this way. An unparameterised one with a permissive default is
usually a silent any, not a sensible fallback. The /typescript skill
applies this as a rule.
๐งช Testing Stackโ
- Unit Testing: Vitest
- Component Testing: @testing-library/react
- E2E Testing: Playwright
- Coverage: @vitest/coverage-v8
๐๏ธ Database & Backendโ
- Database: Supabase (PostgreSQL)
- ORM: Supabase client (direct)
- Validation: Zod schemas
โ๏ธ State Managementโ
- Server State: @tanstack/react-query
- Global State: Zustand
- Form State: @tanstack/react-form
๐ฆ Package Versionsโ
See package.json for specific version requirements. Key dependencies:
{
"dependencies": {
"next": "^16.3.5",
"react": "^19.3.0",
"@tanstack/react-query": "^5.85.5",
"zustand": "^5.0.7"
},
"devDependencies": {
"typescript": "^7.0.2",
"vitest": "^2.1.8",
"@testing-library/react": "^16.1.0",
"playwright": "^1.63.0"
}
}
๐จ UI Librariesโ
- Design System: Custom (Radix UI primitives)
- Icons: Lucide React
- Forms: React Hook Form
- Tables: TanStack Table
๐ง Development Toolsโ
- Linting: ESLint 9+
- Formatting: Prettier
- Monorepo: Turborepo
- Documentation: Docusaurus
Version Requirementsโ
Node.jsโ
- Consumer minimum: 20.0.0, matching the packages'
enginesrequirement - Repository version: 24, pinned in
.nvmrc - Package manager minimum for consumers: pnpm 9, matching the root
enginesrequirement - Repository package manager: pnpm 11.23.0, pinned in
package.json#packageManager
TypeScriptโ
- Version: 5.0+
- Strict Mode: Enabled
- Target: ES2022+