Take a moment to think about this
In this part, we'll turn Part 1's static list into a data-driven app. Bookmark data will be managed through a lib/db.ts data layer that simulates a module-level array, and form submissions via Server Actions will insert/delete directly on the server. You'll also learn progressive enhancement, so the form keeps working even with client-side JavaScript disabled. On top of that, we'll add filtering to the home page via search params like ?tag=docs, and use revalidatePath so the UI updates automatically whenever the data changes.
Let's build it
Create lib/db.ts, keeping the bookmarks array at module scope and exporting getBookmarks(), addBookmark(), and deleteBookmark() functions (a real project could use SQLite/Prisma, but here we'll keep it simple with an in-memory array). In app/actions.ts, write createBookmark and removeBookmark Server Actions with the "use server" directive at the top, and call revalidatePath("/"). In app/bookmarks/new/page.tsx, create a form and wire it to action={createBookmark}. Update app/page.tsx to accept the searchParams prop and filter the list by the tag query. For each bookmark card, wire up a delete button to the removeBookmark action.
Code Example
// lib/db.ts
type Bookmark = { id: string; title: string; url: string; tags: string[] };
let bookmarks: Bookmark[] = [
{ id: "1", title: "Next.js Docs", url: "https://nextjs.org", tags: ["docs"] },
];
export function getBookmarks(tag?: string) {
if (!tag) return bookmarks;
return bookmarks.filter((b) => b.tags.includes(tag));
}
export function addBookmark(data: Omit<Bookmark, "id">) {
bookmarks.push({ id: crypto.randomUUID(), ...data });
}
export function deleteBookmark(id: string) {
bookmarks = bookmarks.filter((b) => b.id !== id);
}
// app/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { addBookmark, deleteBookmark } from "@/lib/db";
export async function createBookmark(formData: FormData) {
const title = String(formData.get("title") ?? "").trim();
const url = String(formData.get("url") ?? "").trim();
if (!title || !url) return;
addBookmark({ title, url, tags: [] });
revalidatePath("/");
}
export async function removeBookmark(id: string) {
deleteBookmark(id);
revalidatePath("/");
}Submitting the form adds a new bookmark card to the home page, and the list filters correctly by the tag query param.5-Minute Try-It
Switch between /?tag=docs and /?tag=reference in the URL bar and confirm within 5 minutes that the filtered results differ.
A quick word of caution
The in-memory array loses its data on every server restart — for a real production project, you should use an actual database like Prisma/SQLite, as covered in the database-layer lesson.