Title: Building a 'Smart' Markdown Note-Taker with Next.js 15 and OpenAI
Source: Dev.to
Intro
In 2025, a standard CRUD app isn’t enough to impress. Users expect applications to think. Today, we’re going to build a Smart Note‑Taking App that doesn’t just store Markdown but uses AI to automatically generate tags and summaries for your notes.
The Stack
- Frontend/Backend: Next.js 15 (App Router)
- Styling: Tailwind CSS
- Database: Prisma + PostgreSQL (via Supabase)
- AI: OpenAI API
1. Setting Up the Project
First, initialize your project with the latest Next.js features:
npx create-next-app@latest smart-notes --typescript --tailwind --eslint
cd smart-notes
npm install @prisma/client lucide-react openai
npx prisma init
2. The Database Schema
We need a simple but effective model. In prisma/schema.prisma:
model Note {
id String @id @default(cuid())
title String
content String @db.Text
tags String[]
summary String? @db.Text
createdAt DateTime @default(now())
}
3. The “Smart” Logic (Server Action)
Instead of a separate API route, we’ll use a Next.js Server Action. This keeps our logic bundled and secure on the server.
Create a file at app/actions/notes.ts:
"use server";
import { PrismaClient } from "@prisma/client";
import OpenAI from "openai";
const prisma = new PrismaClient();
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function createSmartNote(formData: FormData) {
const title = formData.get("title") as string;
const content = formData.get("content") as string;
// AI Magic: Generate Summary and Tags
const aiResponse = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content:
"Analyze the following note. Return a JSON object with a 'summary' (1 sentence) and 'tags' (array of strings).",
},
{ role: "user", content: content },
],
response_format: { type: "json_object" },
});
const { summary, tags } = JSON.parse(
aiResponse.choices[0].message.content || "{}"
);
const note = await prisma.note.create({
data: { title, content, summary, tags },
});
return note;
}
4. The UI: Clean and Functional
In app/page.tsx, create a simple form to handle the input. We’ll use useFormStatus to show a loading state while the AI is “thinking.”
import { createSmartNote } from "./actions/notes";
export default function Home() {
return (
<main className="p-8">
<h1 className="text-2xl font-bold mb-4">Smart Markdown Notes 📝</h1>
<form action={createSmartNote} className="space-y-4">
<div>
<label htmlFor="title" className="block font-medium">
Title
</label>
<input
id="title"
name="title"
type="text"
required
className="mt-1 block w-full rounded border p-2"
/>
</div>
<div>
<label htmlFor="content" className="block font-medium">
Content (Markdown)
</label>
<textarea
id="content"
name="content"
rows={6}
required
className="mt-1 block w-full rounded border p-2"
/>
</div>
<button
type="submit"
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>
Save Smart Note
</button>
</form>
{/* Logic to fetch and map notes would go here */}
</main>
);
}
5. Why This Matters in 2025
- Server Actions: Eliminate the need for complex
useEffectand fetch calls. - AI Integration: Move from “Full Stack” to “AI‑Native” by embedding intelligence directly into the data flow.
- Type Safety: TypeScript ensures that our AI response matches our database schema.
Conclusion
Building full‑stack apps today is about orchestrating services. By combining the power of Next.js with AI, you can create tools that provide actual utility beyond just data storage.