Dependencies
This page explains what each group of packages in package.json does and how the repository stops unused ones from piling up.
Read this page when you want to know why a package is in package.json, or before you add a new one. package.json declares 81 runtime dependencies and 26 dev dependencies, and Bun 1.2.2 installs them (packageManager).
The sections below group the packages by the job they do and give the real versions.
Two checks keep the list from filling up with dead entries. scripts/check-unused-deps.ts fails when you add a runtime dependency that nothing imports. knip.json configures a broader scan for unused exports and files.
Framework and runtime
| Package | Declared version | What it does |
|---|---|---|
next | ^16.1.1 | App Router, route handlers, next build |
react / react-dom | ^19.2.3 | React 19 |
typescript | ^5.9.3 | Dev only. tsc --noEmit is the typecheck |
@t3-oss/env-nextjs | ^0.13.10 | Parses typed environment variables in env.ts |
server-only | ^0.0.1 | The marker that check-client-bundle-server-only.ts looks for when it traces imports |
@sentry/nextjs | ^10.40.0 | Error reporting |
Database
Prisma is pinned to one exact version across three packages:
@prisma/client 7.4.2
@prisma/adapter-pg 7.4.2
prisma 7.4.2None of them has a caret. A minor release therefore can't pull the client out of step with the engine.
Together they handle the schema, the migrations and the driver adapter for pg.
decimal.js-light (^2.5.1) does the decimal arithmetic behind ~/utils/money.
@types/pg (^8.16.0) provides types for the adapter's driver.
You should know about one inconsistency. The db:seed script runs this:
tsx prisma/seed.tspackage.json does not declare tsx, so it has to come from your environment. The prisma.seed entry in the same file runs this instead:
bun prisma/seed.tsThe two ways to run the seed use different runners.
Commerce and payments
stripe (^20.3.1) handles checkout sessions, payment intents and webhook signature checks.
svix (^1.96.1) signs outbound webhooks. jose (^6.1.3) signs and verifies JWTs.
better-auth (^1.4.19) manages sessions, sign-in and account records.
@upstash/redis (^1.36.0) and @upstash/ratelimit (^2.0.7) provide the cache and the rate limiter. checkRateLimit fails open when Redis is unreachable, so a Redis outage does not block sign-in.
AI
The AI layer uses the AI SDK 4 line. Three providers are connected, and you can choose between them:
ai ^4.3.19
@ai-sdk/react ^1.2.12
@ai-sdk/openai ^1.3.24
@ai-sdk/anthropic ^1.2.12
@ai-sdk/google ^1.2.22
streamdown ^2.3.0
use-stick-to-bottom ^1.1.3streamdown renders markdown as it streams in. use-stick-to-bottom keeps the chat scrolled to the newest message.
UI
The UI primitives come from radix-ui (^1.4.3). Two Radix packages have their own versions: @radix-ui/react-collapsible (^1.1.12) and @radix-ui/react-tabs (^1.1.13).
Styling uses tailwindcss (^4.1.18, dev) together with @tailwindcss/postcss, @tailwindcss/typography and tailwindcss-animate. The cx helper in ~/utils/cva is built on cva and tailwind-merge (^3.4.0).
Icons come from lucide-react (^0.575.0), and scripts/build-icons.ts compiles them into a sprite. Application code imports <Icon /> and never imports lucide-react directly. The icon-imports discipline rule enforces this.
These packages handle tables and forms:
@tanstack/react-table(^8.21.3)react-hook-form(^7.70.0)@hookform/resolvers(^3.10.0)zod(^4.3.5)
The rich-text editor is TipTap 3, which comes as six packages:
@tiptap/react
@tiptap/starter-kit
@tiptap/pm
@tiptap/extension-image
@tiptap/extension-link
@tiptap/markdownThe other inputs use react-day-picker (^9.13.0), react-colorful, react-phone-number-input, react-dropzone and cmdk.
These packages handle motion and charts:
motion(^12.38.0)recharts(^3.6.0)@number-flow/reactcanvas-confettiembla-carousel-react
Drag and drop uses @atlaskit/pragmatic-drag-and-drop (^1.7.7) and its hitbox package.
These packages handle state and URLs:
nuqs(^2.8.6) keeps state in search paramsswr(^2.3.8) fetches data on the clientzsa(^0.6.0) andzsa-reactrun server actionssonner(^2.0.7) shows toastsnext-themes@mantine/hooks
One entry has no version range at all. cva is declared as "beta", which is a dist-tag, so you get whatever the current beta is when you install.
Content, email and media
@content-collections/core, /mdx and /next compile MDX content at build time. rehype-slug and rehype-autolink-headings run as part of that pipeline.
@react-email/components (^1.0.3) builds the email templates in emails/, and resend (^6.9.2) delivers them. react-email (dev) runs the preview server behind bun run email.
@aws-sdk/client-s3 and @aws-sdk/lib-storage (^3.962.0) talk to S3-compatible object storage.
satori (^0.13.2) renders OG images and works with loadGoogleFont in config/fonts.ts.
sanitize-html (^2.17.5) cleans HTML, and rss (^1.2.2) generates the feeds.
Background work and analytics
inngest(^3.54.0) runs the crons and domain-event handlers infunctions/@orpc/server/@orpc/shared(^1.13.9) serve the narrow/api/rpcAPIposthog-js(^1.314.0) collects client analytics@atproto/api(^0.15.27) posts to Bluesky for the social modulewretch(^3.0.6) is the HTTP client for outbound integration calls
Utilities
These are the general-purpose helpers:
date-fns(^4.1.0)change-casenanoid(^3.3.11)plur@primoui/utils
The no-primoui-formatters discipline rule stops code from using @primoui/utils for formatting. That job belongs to lib/formatters.ts.
Tooling
@biomejs/biome (^1.9.4) formats and lints the code.
vitest (^4.0.18) runs the tests, together with jsdom (^29.1.1) and vite (^7.3.1).
lefthook (^1.7.0) installs the git hooks. knip (^5.64.4) scans for unused code.
The remaining dev dependencies support the scripts and the build:
globfs-extranode-html-parserdotenvschema-dtspostcss
The zsa patch
The repository patches one dependency:
"patchedDependencies": {
"zsa@0.6.0": "patches/zsa@0.6.0.patch"
}That file is the only one in patches/.
The patch exists because the type definitions don't match. zsa 0.6.0 ships types written for zod 3, but the repository runs zod 4.
The patch rewrites dist/index.d.ts. It declares local AnyZodObject and objectUtil.extendShape shims, and it removes the zod-3-only generic parameters from TZodMerge and the instanceofZodType* helpers.
The patch changes types only, and no runtime behaviour changes. Bun applies it when you install.
Guarding against unused dependencies
scripts/check-unused-deps.ts runs as bun run lint:deps, which is part of the lint:cleanliness group.
The script searches every .ts, .tsx, .mjs and .js file for import specifiers and reduces each one to its top-level package name. It then compares that set with the dependencies in package.json. It does not check dev dependencies.
Some packages are used by configuration instead of an import. The script allowlists those itself:
const ALLOWLIST = [
/^@types\//,
/^tailwindcss$/,
/^postcss/,
/^autoprefixer$/,
/^sharp$/, // next/image runtime
/^@tiptap\/pm$/, // ProseMirror core — required peer of @tiptap/react, imported transitively
]scripts/unused-deps-baseline.txt does not exist in the repository, and the script treats a missing baseline as an empty one.
The check therefore allows no exceptions today. Every runtime dependency is imported somewhere, and if you add one that isn't, the build fails right away.
The wider knip scan
knip.json configures bun run lint:knip. Knip starts from these entry points:
- the Next.js route conventions
functions/scripts/prisma/instrumentation.tsmiddleware.ts- the config files
It scans these project folders:
app
components
server
services
lib
hooks
utils
events
typesIt ignores tests, stories and .d.ts files, and ignoreDependencies is empty.
Knip does not run as part of bun run verify or bun run lint. You run it when you want to.
Inside the gate, the harness's own check-dead-components.ts, check-orphan-exports.ts and check-dead-server-actions.ts find dead code.
Before adding a dependency
Real source code must import every runtime dependency, or lint:deps fails on its next run. If configuration uses the package instead, add it to the script's ALLOWLIST and write down why in that entry.
If you patch a dependency, as with zsa, its file in patches/ needs another look every time you upgrade that package.
Checking that the package works is not enough. Ask this instead:
Which file will import it, and what will this repository do on the day the package is no longer maintained?
Related
Stack
See the packages and versions Litestore runs on and the design decisions behind them.
Harness
See the 25 CI checks that run on your fork and the 8-guide rule set behind them.
Linting
See how Biome formats and lints the code, what bun run lint and bun run verify run, and which lefthook hooks fire on commit and push.