Build a Blog with Nextly in 10 Minutes

N
Nextly Team
··8 min read

In this tutorial, we will build a complete blog with posts, categories, and an author field using Nextly. By the end, you will have a working blog with structured content, relational data, a rich text editor, and a Next.js frontend that displays it all. No external services, no third-party APIs, everything runs inside your Next.js app.

What we are building

Our blog will have three collections:

  • Posts with a title, slug, rich text content, featured image, author relationship, category relationship, and a draft/published status
  • Categories with a name, slug, and description
  • Users (built-in) serving as the author for each post

We will define the schema in TypeScript, create some content through the admin panel, and then build two pages: a blog listing page and an individual post page.

Step 1: Install Nextly

Start by creating a new Nextly project. Open your terminal and run:

bash
npx create-nextly-app@latest my-blog

The CLI will prompt you to choose a database. For this tutorial, SQLite is the simplest option since it requires no external database server. Select it and let the setup complete.

bash
cd my-blog
npm run dev

Open http://localhost:3000/admin in your browser. You will see the Nextly admin panel. Create your first admin user account by filling out the registration form.

Step 2: Define the Posts collection

Open your project in your editor and find the collections directory. Create a new file for the Posts collection:

typescript
// collections/Posts.ts
import {
  defineCollection,
  text,
  richText,
  upload,
  relationship,
  select,

const Posts = defineCollection({ slug: "posts", admin: { useAsTitle: "title", }, fields: [ text({ name: "title", required: true, }), text({ name: "slug", required: true, unique: true, admin: { position: "sidebar", }, }), richText({ name: "content", }), upload({ name: "featuredImage", relationTo: "media", }), relationship({ name: "author", relationTo: "users", required: true, }), relationship({ name: "categories", relationTo: "categories", hasMany: true, }), select({ name: "status", options: [ { label: "Draft", value: "draft" }, { label: "Published", value: "published" }, ], defaultValue: "draft", admin: { position: "sidebar", }, }), ], });

export default Posts; `

This defines a Posts collection with everything a blog needs. The title is a required text field. The slug is a unique text field shown in the admin sidebar. The content field uses the Lexical rich text editor. The featuredImage is an upload field linked to the built-in media collection. The author is a required relationship to the users collection. The categories field is a relationship that allows multiple selections. The status is a select field for managing the publish workflow.

Step 3: Define the Categories collection

Create another file for categories:

typescript
// collections/Categories.ts

const Categories = defineCollection({ slug: "categories", admin: { useAsTitle: "name", }, fields: [ text({ name: "name", required: true, }), text({ name: "slug", required: true, unique: true, }), textarea({ name: "description", }), ], });

export default Categories; `

Then register both collections in your Nextly config:

typescript
// nextly.config.ts
import { defineConfig } from "@revnixhq/nextly/config";
import Posts from "./collections/Posts";

export default defineConfig({ collections: [Posts, Categories], }); `

Restart your dev server, and Nextly will automatically create the database tables for your new collections.

Step 4: Create content in the admin panel

Open http://localhost:3000/admin and you will see your new collections in the sidebar navigation.

Start by creating a few categories. Click on "Categories" in the sidebar, then click "Create New." Add categories like "Engineering," "Tutorials," and "News." Fill in the name, slug, and a short description for each one.

Next, create your first blog post. Click on "Posts" in the sidebar, then "Create New." You will see the full editing interface:

  • Type your post title in the title field
  • Enter a URL-friendly slug
  • Use the rich text editor to write your content. You can add headings, bold text, code blocks, images, lists, and more using the toolbar or markdown shortcuts
  • Upload a featured image by clicking the featuredImage field and selecting a file
  • Select yourself as the author
  • Choose one or more categories
  • Set the status to "Published" when you are ready

Save the post. You now have structured content stored in your database, ready to query from your Next.js frontend.

Step 5: Display a single post

Now let us build the frontend. Create a dynamic route for individual blog posts:

typescript
// app/blog/[slug]/page.tsx
import { getNextly } from "@revnixhq/nextly";

export default async function BlogPostPage({ params, }: { params: Promise<{ slug: string }>; }) { const { slug } = await params; const nextly = await getNextly();

const result = await nextly.find({ collection: "posts", where: { slug: { equals: slug }, status: { equals: "published" }, }, depth: 2, richTextFormat: "html", });

const post = result.docs[0]; if (!post) return notFound();

return ( <article className="max-w-2xl mx-auto py-12 px-4"> <h1 className="text-4xl font-bold mb-4">{post.title}</h1> <div className="text-sm text-gray-500 mb-8"> By {post.author.name} | {post.categories.map((c) => c.name).join(", ")} </div> {post.featuredImage && ( <img src={post.featuredImage.url} alt={post.title} className="w-full rounded-lg mb-8" /> )} <div className="prose" dangerouslySetInnerHTML={{ __html: post.content }} /> </article> ); } `

This page uses the Direct API, which means the data query happens directly on the server with zero network overhead. The depth: 2 parameter tells Nextly to resolve relationships, so post.author returns the full user object and post.categories returns the full category objects instead of just IDs. The richTextFormat: "html" option tells Nextly to return the rich text content as an HTML string, ready for rendering.

Step 6: Add a post listing page

Create an index page that shows all published posts:

typescript
// app/blog/page.tsx
import { getNextly } from "@revnixhq/nextly";

export default async function BlogListPage() { const nextly = await getNextly();

const result = await nextly.find({ collection: "posts", where: { status: { equals: "published" }, }, sort: "-createdAt", depth: 1, richTextFormat: "html", });

return ( <div className="max-w-4xl mx-auto py-12 px-4"> <h1 className="text-3xl font-bold mb-8">Blog</h1> <div className="grid gap-8"> {result.docs.map((post) => ( <Link key={post.id} href={/blog/${post.slug}} className="block p-6 border rounded-lg hover:shadow-md transition-shadow" > <h2 className="text-xl font-semibold mb-2">{post.title}</h2> <div className="text-sm text-gray-500 mb-3"> {post.categories.map((c) => c.name).join(", ")} </div> {post.featuredImage && ( <img src={post.featuredImage.url} alt={post.title} className="w-full rounded mb-4" /> )} </Link> ))} </div> </div> ); } `

This queries all published posts sorted by creation date (newest first), resolves one level of relationships to get category names and image URLs, and renders each post as a linked card.

The result

You now have a working blog built entirely within your Next.js application. Here is what you have accomplished:

  • Two content collections (Posts and Categories) defined in TypeScript with full type safety
  • Relational data connecting posts to categories and authors
  • A rich text editor for writing blog content with formatting, images, and code blocks
  • An admin panel at /admin for managing all your content
  • A frontend with a listing page and individual post pages using Server Components
  • Zero external dependencies for content management, everything runs inside your app

From here, you can extend your blog with tags, comments, search, pagination, or any other feature. Because Nextly collections are just TypeScript definitions, adding new fields or collections follows the same pattern you have already learned.

The full source code for this tutorial is available in the Nextly examples repository on GitHub.

Start building with Nextly

Free, open source, and yours to own. No sign-up required.

>_npx create-nextly-app@latest