Skip to main content
Nextly

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

Next.js
TypeScript
Node.js
Drizzle ORM
React
PostgreSQL
Turborepo
Docker
Playwright
Tailwind CSS

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

01

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.

02

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.

03

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.

Command Palette

Search for a page or action...