Thuta Learning
ProjectsWeb Developmentintermediate

Mini Project — Link Stash (Part 3): Polish and Deploy

Relax. We'll talk through this in plain words — no textbook voice.

What you'll walk away with

  • Apply Mini Project — Link Stash (Part 3): Polish and Deploy in a real project
  • Write the code yourself and run it
  • Build out an entire project step by step

Let's think about this for a moment

For an app to be called 'done,' it needs more than just features — it needs UX polish too. In this step, we'll add loading.tsx and a Suspense boundary for when the network is slow, and show not-found.tsx when a dynamic route's id doesn't match anything. To make link previews look sharp when shared on social media, we'll add generateMetadata and Open Graph tags. Finally, we'll check next build locally and push to Vercel so the whole project goes live at a URL anyone can visit.

Let's build it for real

Create app/loading.tsx with a skeleton UI, and add app/bookmarks/[id]/not-found.tsx to show up when an id isn't found (trigger it by calling notFound() inside page.tsx). In app/layout.tsx, add a metadata export with title/description/openGraph as the site-wide default, then use a generateMetadata() function on the bookmark detail page to return the bookmark's title as dynamic metadata. Run npm run build locally and check for errors or warnings. Create a GitHub repo, push your code, then import it from the Vercel dashboard and deploy — since this project doesn't need any environment variables yet, you can deploy with the default settings.

Code Example

tsx
// app/bookmarks/[id]/page.tsx
import { notFound } from "next/navigation";
import { getBookmarks } from "@/lib/db";
import type { Metadata } from "next";

type Props = { params: { id: string } };

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const bookmark = getBookmarks().find((b) => b.id === params.id);
  if (!bookmark) return { title: "Bookmark Not Found" };
  return {
    title: `${bookmark.title} — Link Stash`,
    openGraph: { title: bookmark.title, description: bookmark.url },
  };
}

export default function BookmarkDetail({ params }: Props) {
  const bookmark = getBookmarks().find((b) => b.id === params.id);
  if (!bookmark) notFound();

  return (
    <article>
      <h2>{bookmark.title}</h2>
      <a href={bookmark.url}>{bookmark.url}</a>
    </article>
  );
}

// app/bookmarks/[id]/not-found.tsx
export default function NotFound() {
  return <p>ဒီ bookmark ကို ရှာမတွေ့ပါ — id မှားနေတာ ဖြစ်နိုင်ပါတယ်။</p>;
}
You should see
Open the live Vercel URL in your browser and the whole Link Stash app — add, delete, filter, detail pages, metadata preview, all of it — will be working in production.

Try it in 5 minutes

Type a wrong id (like /bookmarks/999) into your browser and confirm within 5 minutes that the not-found.tsx page shows up.

A quick word of caution

Calling notFound() inside a component immediately halts that function's execution — keep in mind that none of the code after notFound() will run.

Easy traps

  • Forgetting to write generateMetadata() as an async function and writing a synchronous function instead, which triggers a type error
  • Skipping a local next build run before deploying to Vercel, only to discover the build error after deploying

Now try it yourself

Type a wrong id (like /bookmarks/999) into your browser and confirm within 5 minutes that the not-found.tsx page shows up.

You'll know it worked when: Open the live Vercel URL in your browser and the whole Link Stash app — add, delete, filter, detail pages, metadata preview, all of it — will be working in production.

Mini Project — Link Stash (Part 3): Polish and Deploy | Thuta Learning