
Nextly
Nextly is a TypeScript-first, Next.js-native headless CMS that combines a visual UI builder with a powerful code-first approach, giving developers and content teams equal flexibility. It's designed specifically for teams looking for a modern headless CMS for Next.js.
At Revnix, we needed a Next.js CMS that felt truly native while supporting both rapid content editing and deep developer control. Existing solutions — including Payload CMS — didn't fully align with our workflow, so I took the lead on building one from scratch.
I architected and developed Nextly as a modular monorepo, featuring a core engine, a full React 19 admin dashboard, a client SDK, and pluggable database adapters for Postgres, MySQL, and SQLite using Drizzle ORM. The system supports dynamic content modeling, a flexible plugin and hook lifecycle, and built-in role-based access control (RBAC). It also leverages Turborepo for build orchestration, Docker for consistent development environments, and Playwright for end-to-end testing.
This project originated from a client need to move away from a rigid and costly WordPress setup that struggled to scale. By replacing it with a fully custom CMS, we reduced dependency on third-party plugins by 60%, improved maintainability, and achieved performance scores exceeding 90+.
Nextly continues to evolve with a clear goal: to provide a flexible, developer-first headless CMS for Next.js that feels native, scalable, and built for modern workflows — not bolted on.
Technologies
Key Highlights
- Replaced a rigid WordPress setup — cut third-party plugin dependency by 60% and pushed performance scores to 90+
- Modular monorepo with a React 19 admin dashboard, pluggable DB adapters, client SDK, flexible plugin/hook lifecycle, and a direct-API layer for querying the CMS via Server Actions — no HTTP round-trip needed
Engineering Challenges
Keeping a Singleton Alive Across Next.js Module Boundaries
Next.js runs RSC and SSR in separate module contexts, so a standard singleton DI container gets silently re-instantiated on each request — every layer ends up with its own disconnected copy. The fix was storing the container on globalThis, making it a true process-level singleton that survives Next.js's module duplication. Without this, services would lose state between a server component and the server action that follows it.
Type-Safe Queries Across Three Database Dialects at Runtime
The database adapter — Postgres, MySQL, or SQLite — is resolved at runtime from an environment variable, but TypeScript's type checking happens at compile time. Drizzle ORM's schema types are dialect-specific (pgTable, mysqlTable, sqliteTable), so keeping full type inference while supporting runtime dialect selection required careful abstraction. The solution was a getDialectTables() factory that returns the correct schema at runtime, backed by shared generic types that work across all three dialects without leaking dialect-specific details.
Row-Level Security Without eval()
RLS policies support custom expressions like record.status === 'published' && record.authorId === userId, which makes them flexible enough for almost any access rule. The obvious approach — eval() — is a security hole that exposes the full module scope to any expression. Instead, expressions are compiled with new Function() against a strict whitelist of variables (record, userId, roleIds, userTeamIds), creating a minimal sandbox with no access to module internals or globals.