# Installation

Install Nextly in a new or existing Next.js project with step-by-step instructions.

There are two ways to install Nextly: scaffold a new project with the CLI, or add Nextly to an existing Next.js app manually. Both paths take a few minutes.

## Option A: Create a New Project (Recommended)

The `create-nextly-app` CLI scaffolds a complete Nextly project with your choice of database and template. Storage defaults to local disk, so you don't need to configure cloud storage to get started.

**pnpm**

```bash
pnpm create nextly-app@alpha my-app
```

**npm**

```bash
npx create-nextly-app@alpha my-app
```

**yarn**

```bash
yarn create nextly-app@alpha my-app
```

**bun**

```bash
bun create nextly-app@alpha my-app
```

The CLI will prompt you to:

1. **Project name** -- defaults to the directory name. Use `.` to scaffold into the current folder.
2. **Template** -- `Blank` (empty config) or `Blog` (a complete blog with posts, authors, categories, and frontend pages). See [Templates](https://nextlyhq.com/docs/templates.md) for what each one ships with.
3. **Schema approach** (only for templates that support both) -- `code-first` (default for Blog) or `visual` (Visual Schema Builder).
4. **Database** -- `SQLite` (default), `PostgreSQL`, or `MySQL`.
5. **Database connection string** -- skipped for SQLite (defaults to `file:./data/nextly.db`); required for PostgreSQL/MySQL.

Once the CLI completes, change into the project and start the dev server:

**pnpm**

```bash
cd my-app
pnpm dev
```

**npm**

```bash
cd my-app
npm run dev
```

**yarn**

```bash
cd my-app
yarn dev
```

**bun**

```bash
cd my-app
bun run dev
```

### Create the super admin

The first time you run the dev server, the database is empty -- there's no admin account yet. Open the setup page to create one:

```
http://localhost:3000/admin/setup
```

Enter an email, password, and name. The first user is bootstrapped with a `Super Admin` role that has every permission. After that, you'll be redirected to the login screen at `/admin`.

### Default storage is local disk

Out of the box, Nextly uses your project's `./public/uploads/` directory as the media store -- no env vars, no cloud account, no credentials. Files you upload through the admin's media library land there and are served by Next.js as static assets. When you're ready to move to S3, Vercel Blob, or Uploadthing, see [Media & Storage](https://nextlyhq.com/docs/guides/media-storage.md).

## Option B: Add to an Existing Next.js Project

### 1. Install Packages

Install the runtime, the admin panel and what it expects from your project, and `esbuild`, which the CLI and the dev server use to load `nextly.config.ts`:

**pnpm**

```bash
pnpm add nextly @nextlyhq/admin @nextlyhq/ui @tanstack/react-query lucide-react
pnpm add -D esbuild
```

**npm**

```bash
npm install nextly @nextlyhq/admin @nextlyhq/ui @tanstack/react-query lucide-react
npm install -D esbuild
```

**yarn**

```bash
yarn add nextly @nextlyhq/admin @nextlyhq/ui @tanstack/react-query lucide-react
yarn add -D esbuild
```

**bun**

```bash
bun add nextly @nextlyhq/admin @nextlyhq/ui @tanstack/react-query lucide-react
bun add -d esbuild
```

Then install your database driver:

**PostgreSQL**

```bash
pnpm add @nextlyhq/adapter-postgres pg
```

**MySQL**

```bash
pnpm add @nextlyhq/adapter-mysql mysql2
```

**SQLite**

```bash
pnpm add @nextlyhq/adapter-sqlite better-sqlite3
```

On pnpm 10 or later, allow the native packages to run their build scripts, or `better-sqlite3` has no compiled binding and the app fails at boot, and `sharp`, `esbuild` and `unrs-resolver` fall back to slower paths. On pnpm 10.6 or later, add to `pnpm-workspace.yaml` (create it with `packages: []` if you have none):

```yaml title="pnpm-workspace.yaml"
allowBuilds:
  better-sqlite3: true
  esbuild: true
  sharp: true
  unrs-resolver: true
onlyBuiltDependencies:
  - better-sqlite3
  - esbuild
  - sharp
  - unrs-resolver
```

pnpm 11 reads `allowBuilds`, pnpm 10.6+ reads `onlyBuiltDependencies`. On pnpm 10.0 to 10.5, put the same list in `package.json` instead, as `"pnpm": { "onlyBuiltDependencies": ["better-sqlite3", "esbuild", "sharp", "unrs-resolver"] }`, or upgrade pnpm. npm, yarn and bun need none of this.

Cloud storage is optional -- Nextly's default is local disk under `./public/uploads/`. Install a storage adapter only if you need one:

**S3 / R2 / DigitalOcean Spaces**

```bash
pnpm add @nextlyhq/storage-s3
```

**Vercel Blob**

```bash
pnpm add @nextlyhq/storage-vercel-blob
```

**Uploadthing**

```bash
pnpm add @nextlyhq/storage-uploadthing
```

### 2. Create `nextly.config.ts`

Create a `nextly.config.ts` file in your project root:

```typescript title="nextly.config.ts"
import { defineConfig } from "nextly/config";

export default defineConfig({
  collections: [],
  singles: [],

  typescript: {
    outputFile: "./src/types/generated/nextly-types.ts",
  },
});
```

### 3. Update `next.config.ts`

Add `serverExternalPackages` so Next.js doesn't try to bundle server-only dependencies:

```typescript title="next.config.ts"
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  serverExternalPackages: [
    "nextly",
    "@nextlyhq/adapter-drizzle",
    "@nextlyhq/adapter-postgres",
    "@nextlyhq/adapter-mysql",
    "@nextlyhq/adapter-sqlite",
    "drizzle-orm",
    "drizzle-kit",
    "pg",
    "mysql2",
    "better-sqlite3",
    "bcryptjs",
    "sharp",
    "esbuild",
  ],
};

export default nextConfig;
```

Drop adapters and drivers your project doesn't actually use.

### 4. Set Up Admin Routes

Create the admin panel page and layout:

```tsx title="src/app/admin/[[...params]]/page.tsx"
"use client";

import "@nextlyhq/admin/style.css";
import { RootLayout, QueryProvider, ErrorBoundary } from "@nextlyhq/admin";

export default function AdminPage() {
  return (
    <ErrorBoundary
      onError={(error, errorInfo) => {
        console.error("Admin error:", error, errorInfo);
      }}
    >
      <QueryProvider>
        <RootLayout />
      </QueryProvider>
    </ErrorBoundary>
  );
}
```

```tsx title="src/app/admin/[[...params]]/layout.tsx"
import { getBrandingCss } from "nextly/config";

import config from "../../../../nextly.config";

const brandingCss = getBrandingCss(config.admin?.branding);

export default function AdminLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      {brandingCss && (
        <style dangerouslySetInnerHTML={{ __html: brandingCss }} />
      )}
      {children}
    </>
  );
}
```

Create the admin API catch-all route:

```typescript title="src/app/admin/api/[[...params]]/route.ts"
import { createDynamicHandlers } from "nextly/runtime";

import nextlyConfig from "../../../../../nextly.config";

const handlers = createDynamicHandlers({ config: nextlyConfig });

export const GET = handlers.GET;
export const POST = handlers.POST;
export const PUT = handlers.PUT;
export const PATCH = handlers.PATCH;
export const DELETE = handlers.DELETE;
export const OPTIONS = handlers.OPTIONS;
```

The admin's media library talks to its own route, which the catch-all above does not serve. Without it, uploads fail:

```typescript title="src/app/admin/api/media/[[...path]]/route.ts"
import { createMediaHandlers } from "nextly/api/media-handlers";

import nextlyConfig from "../../../../../../nextly.config";

// Checked against the caller's media permissions, except the raw bytes of a
// publicly servable file (a web font), which a browser fetches without a session.
const handlers = createMediaHandlers({ config: nextlyConfig, requireAuth: true });

export const GET = handlers.GET;
export const POST = handlers.POST;
export const PATCH = handlers.PATCH;
export const DELETE = handlers.DELETE;
```

Optionally, add a health check for your load balancer or uptime monitor:

```typescript title="src/app/api/health/route.ts"
export { GET, HEAD } from "nextly/api/health";
```

Then start Nextly when the server starts. Next.js runs `register()` once per server instance and finishes it before the instance serves a request, so the health route above, and any code that reads through Nextly, finds it already running. Without it, Nextly starts on the first request that carries its config, and the health route answers 503 until then. Put the file beside `app/`: `src/instrumentation.ts`, or `instrumentation.ts` in the project root when there is no `src/` (then import `./nextly.config`). If the app already has one, add these lines to its `register()`:

```typescript title="src/instrumentation.ts"
export async function register(): Promise<void> {
  // Nextly needs Node.js; the Edge runtime cannot load it.
  if (process.env.NEXT_RUNTIME !== "nodejs") return;

  const { registerNextCacheRevalidator } = await import("nextly/runtime");
  registerNextCacheRevalidator();

  const { createRegister } = await import("nextly");
  const { default: config } = await import("../nextly.config");
  await createRegister(config)();
}
```

Next.js skips `register()` during `next build`, so this opens no database connection at build time. On Next.js 14 the hook runs only when `next.config` sets `instrumentationHook: true` in its `experimental` object (add it beside any keys already there), and if the config sets `pageExtensions`, name the file with one of them (`instrumentation.page.ts` for `page.ts`).

### 5. Set Up Environment Variables

Create a `.env` file with your database and auth configuration:

```bash title=".env"
# Database
DB_DIALECT=postgresql
DATABASE_URL=postgresql://user:password@localhost:5432/nextly

# Authentication (REQUIRED, min 32 chars)
# Generate with: openssl rand -base64 32
NEXTLY_SECRET=change-me-generate-a-secure-secret

# Application URL
NEXT_PUBLIC_APP_URL=http://localhost:3000
```

That's the minimum. For storage, email, and other optional settings see [Environment Variables](https://nextlyhq.com/docs/configuration/environment.md).

### 6. Run Migrations

Initialize the database schema. Nextly seeds default RBAC roles and permissions on first run:

**pnpm**

```bash
pnpm nextly migrate
```

**npm**

```bash
npx nextly migrate
```

**yarn**

```bash
yarn nextly migrate
```

**bun**

```bash
bunx nextly migrate
```

In development, Nextly auto-syncs schema changes when you save `nextly.config.ts`, so you usually only need `nextly migrate` for initial setup and production deploys. See [Production migrations](https://nextlyhq.com/docs/guides/production-migrations.md) for the production flow.

### 7. Verify

Start the development server:

**pnpm**

```bash
pnpm dev
```

**npm**

```bash
npm run dev
```

**yarn**

```bash
yarn dev
```

**bun**

```bash
bun run dev
```

Open [http\://localhost:3000/admin/setup](http://localhost:3000/admin/setup) to create the first super admin account, then log in at [http\://localhost:3000/admin](http://localhost:3000/admin).

## Requirements

| Requirement | Version                                                                     |
| ----------- | --------------------------------------------------------------------------- |
| Node.js     | `^20.19.0 \|\| ^22.12.0 \|\| >=24.0.0` (not Node 23)                        |
| Next.js     | 16+ (App Router required)                                                   |
| React       | 19+                                                                         |
| TypeScript  | 5+                                                                          |
| Database    | PostgreSQL (recommended for production), MySQL, or SQLite (local demo only) |

### Yarn Plug'n'Play

Nextly installs and generates types under Yarn's Plug'n'Play, but Next.js's default bundler, Turbopack, does not support Plug'n'Play: it is listed among Turbopack's [unsupported features](https://nextjs.org/docs/app/api-reference/turbopack#unsupported-and-unplanned-features), and `next build` stops with "Could not find the Next.js package". Run Next.js with webpack instead (`next dev --webpack`, `next build --webpack`), or set `nodeLinker: node-modules` in `.yarnrc.yml` and install again.

## Next Steps

* [Quick Start](https://nextlyhq.com/docs/getting-started/quick-start.md) -- Build a blog, step by step
* [Project Structure](https://nextlyhq.com/docs/getting-started/project-structure.md) -- Understand the file layout
* [Configuration](https://nextlyhq.com/docs/configuration.md) -- Full configuration reference
* [Environment Variables](https://nextlyhq.com/docs/configuration/environment.md) -- All environment variables explained
