Quick Start: Build a Blog in 5 Minutes
Create a blog with posts and categories using Nextly. Define content with code or build it visually.
This guide walks you through building a blog with posts and categories. You will define a content model, run migrations, create content in the admin panel, and query it in a Next.js page.
Prerequisites: Complete the Installation steps first.
1. Define Your Collections
Open nextly.config.ts and define a posts collection and a categories collection:
import {
defineConfig,
defineCollection,
text,
richText,
select,
relationship,
date,
} from "@nextlyhq/nextly/config";
const posts = defineCollection({
slug: "posts",
labels: { singular: "Post", plural: "Posts" },
fields: [
text({ name: "title", required: true }),
text({ name: "slug", required: true, unique: true }),
richText({ name: "content" }),
relationship({ name: "category", relationTo: "categories" }),
select({
name: "status",
options: [
{ label: "Draft", value: "draft" },
{ label: "Published", value: "published" },
],
defaultValue: "draft",
}),
date({ name: "publishedAt" }),
],
});
const categories = defineCollection({
slug: "categories",
labels: { singular: "Category", plural: "Categories" },
fields: [
text({ name: "name", required: true }),
text({ name: "slug", required: true, unique: true }),
],
});
export default defineConfig({
collections: [posts, categories],
singles: [],
typescript: {
outputFile: "./src/types/nextly-types.ts",
},
});After saving, run the CLI to sync your schema to the database:
npx @nextlyhq/nextly migrate- Open the admin panel at http://localhost:3000/admin
- Navigate to Collections in the sidebar
- Click Create Collection
- Name it
Posts(slug:posts) - Add fields:
title-- Text, requiredslug-- Text, required, uniquecontent-- Rich Textstatus-- Select with options: Draft, PublishedpublishedAt-- Date
- Click Save
- Repeat to create a
Categoriescollection with fields:name-- Text, requiredslug-- Text, required, unique
The Schema Builder creates the database tables automatically. No migration step needed.
2. Add Content
Open the admin panel at http://localhost:3000/admin.
- Go to Categories and create a few categories (e.g., "Technology", "Design")
- Go to Posts and create a post:
- Set a title and slug
- Write some content in the rich text editor
- Select a category
- Set status to "Published"
- Set a publish date
3. Query Content in Your App
Use the Direct API to fetch posts in a Server Component. The Direct API runs on the server -- no HTTP requests, no REST endpoints needed.
import { nextly } from "@nextlyhq/nextly";
export default async function BlogPage() {
const { docs: posts } = await nextly.find({
collection: "posts",
where: {
status: { equals: "published" },
},
sort: "-publishedAt",
limit: 10,
});
return (
<div>
<h1>Blog</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<time>{new Date(post.publishedAt).toLocaleDateString()}</time>
</article>
))}
</div>
);
}Fetch a Single Post
import { nextly } from "@nextlyhq/nextly";
import { notFound } from "next/navigation";
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const { docs } = await nextly.find({
collection: "posts",
where: {
slug: { equals: slug },
status: { equals: "published" },
},
limit: 1,
});
const post = docs[0];
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}4. Add a Single (formerly Global)
Singles are perfect for site-wide settings. Add a blog settings single to your config:
import { defineSingle, text, number } from "@nextlyhq/nextly/config";
const blogSettings = defineSingle({
slug: "blog-settings",
labels: { singular: "Blog Settings" },
fields: [
text({ name: "blogTitle", required: true, label: "Blog Title" }),
text({ name: "tagline", label: "Tagline" }),
number({ name: "postsPerPage", defaultValue: 10 }),
],
});
export default defineConfig({
collections: [posts, categories],
singles: [blogSettings],
// ... rest of config
});Run npx @nextlyhq/nextly migrate to sync the schema.
- In the admin panel, go to Singles
- Click Create Single
- Name it
Blog Settings(slug:blog-settings) - Add fields:
blogTitle(Text, required),tagline(Text),postsPerPage(Number, default: 10) - Click Save
Query the single in your layout:
import { nextly } from "@nextlyhq/nextly";
export default async function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
const settings = await nextly.findGlobal({
slug: "blog-settings",
});
return (
<div>
<header>
<h1>{settings?.blogTitle}</h1>
<p>{settings?.tagline}</p>
</header>
{children}
</div>
);
}What You Built
In 5 minutes, you have:
- Two collections (
postsandcategories) with typed fields - A single (
blog-settings) for site-wide configuration - An admin panel to manage all content at
/admin - Server-rendered pages that query content directly with the Direct API
Next Steps
- Project Structure -- Understand how the project is organized
- Fields -- Explore all available field types
- Direct API -- Full API reference for querying content
- Media Storage -- Set up image and file uploads