I Built Five npm Packages to Solve the Five Problems Every Next.js Backend Ignores
A production-grade toolkit for Next.js backends, and the architectural thinking behind it.
After years of building internal tools and SaaS products with Next.js, I kept copying the same five things across every project. Not because they were hard to write, but because no one had packaged them together in a way that was coherent, composable, and genuinely production-ready.
So I built nx-safe-suite: a monorepo of five independently installable TypeScript packages that handle the foundational concerns of any serious Next.js backend.
This article covers the architecture and the reasoning. The next one goes deep on each package.
The Problem With "Just Use process.env"
Every Next.js tutorial ends the same way: process.env.DATABASE_URL. No validation. No type safety. No guarantee that the variable exists.
In production, this means one of two things happens. Either you catch the error immediately as a hard crash at startup, or worse, you do not. The variable is undefined, it flows silently through your application, and something breaks three layers deep in a way that takes an hour to trace back to its source.
This is not a Next.js problem. It is a discipline problem. And it is exactly the kind of problem that a well-designed package should eliminate at the boundary, not paper over with optional chaining and defensive null checks scattered throughout the codebase.
The same pattern repeats for error responses, authentication, caching, and audit logging. Every team solves them. Almost no team solves them consistently.
What nx-safe-suite Is
Five packages. Five problems. Each one installable on its own.
| Package | Problem it solves |
|---|---|
@nx-safe-suite/env |
Environment variables that are validated, typed, and fail loudly |
@nx-safe-suite/api-response |
API responses that are consistent, RFC-compliant, and predictable |
@nx-safe-suite/route-guard |
Authentication, authorization, and validation without boilerplate |
@nx-safe-suite/server-cache |
Multi-tier caching with invalidation that actually works |
@nx-safe-suite/audit-log |
Structured audit logging that survives compliance reviews |
They compose naturally. None of them requires the others. You can adopt one this week and the rest over the following months.
The Architecture Decision: Why Not a Framework?
The obvious alternative was a framework: a single opinionated abstraction that wraps all five concerns. I chose not to build one, and the reason is worth explaining.
Frameworks impose a contract. When you adopt a framework, you adopt its assumptions about your data model, your auth strategy, your deployment target. Those assumptions are often correct for the common case and deeply wrong for your specific case.
Individual composable packages impose an interface. They define what goes in and what comes out. You decide what happens in between.
This distinction matters at scale. When a framework assumption breaks down, and it always eventually does, you are refactoring across the entire abstraction. When a package interface breaks down, you swap the package.
The Monorepo Structure
All five packages live in a single repository, managed with pnpm workspaces and Turborepo.
nx-safe-suite/
├── packages/
│ ├── env/
│ ├── api-response/
│ ├── route-guard/
│ ├── server-cache/
│ └── audit-log/
└── apps/
└── docs/
Turborepo handles the build graph. When route-guard depends on the error shape from api-response, Turborepo ensures the latter is always built first. The cache means that on a clean run, only changed packages rebuild. A build that would take 22 seconds takes 43 milliseconds on the second pass.
Versioning and publishing are handled by Changesets. Each meaningful change includes a changeset file describing the impact as patch, minor, or major. The release workflow on GitHub Actions assembles those into version bumps and publishes to npm automatically when merged to main.
The Dependency Philosophy
The five packages collectively have zero mandatory runtime dependencies on third-party libraries. Every dependency that exists is either a devDependency (TypeScript, Vitest, tsup) or a peerDependency that the consuming project almost certainly already has.
@nx-safe-suite/env lists zod as a peer. You bring your own Zod.
@nx-safe-suite/route-guard lists both zod and jose as peers. The jose dependency is optional and only needed if you use the built-in JWT verification mode.
The rest have nothing.
This matters for two reasons. First, it avoids version conflicts: you do not end up with two copies of Zod at different versions in your bundle. Second, it keeps the surface area honest. These packages solve specific structural problems. They do not replace your existing toolchain.
Quality Signals
Every package ships with a full test suite written in Vitest. The numbers across the five packages: 115 unit tests, all passing, covering success paths, failure paths, edge cases, and integration behavior between layers.
The TypeScript configuration is strict throughout: strict: true, noUncheckedIndexedAccess: true, noImplicitOverride: true. These are not cosmetic. They are the settings that catch the bugs TypeScript is designed to catch.
The CI pipeline runs on Node 20, 22, and 24. Packages that produce CJS and ESM builds are tested against both module systems.
The Library Mindset
Building a library forces a different kind of thinking than building an application. An application can tolerate internal inconsistency. A library cannot, because its surface area is a contract with every developer who installs it. Every public type, every default value, every error message is a decision that has consequences for someone else's code.
The decisions visible in nx-safe-suite are not incidental. The pluggable interfaces, the peer dependency choices, the RFC 9457 compliance, the Changesets workflow, the sub-42ms Turborepo cache hit: all of them reflect a specific way of thinking about software. Constraints are features. Composability beats comprehensiveness. A well-designed system makes the wrong thing hard to do by default.
Where to Find It
The code is public: github.com/adeutou/nx-safe-suite
The documentation is live at: adeutou.github.io/nx-safe-suite
The packages are on npm under the @nx-safe-suite scope.
The next article goes into each package individually: the problem it solves, the API design, and the implementation choices that are not obvious from the README.