Loading awesome stuff...

Getting Started with Next.js 14 - or How to Stop Worrying and Love the App Router

2025-04-26

#nextjs#react#typescript#tutorial

TL;DR > If you can git init and resist copy-pasting from ChatGPT without reading, you’re 80% of the way there.


1. Why Next.js 14? …and not some hipster meta-framework du jour

  • App Router is stable, finally. Nested layouts, streaming, and Server Components no longer feel like experimental feature flags.
  • Built-in async/await data fetching — no more duct-taping getServerSideProps.
  • Edge runtime + RSC means you can serve a light-speed dashboard to users on Dagobah.
  • First-class TypeScript (because JavaScript without types is basically a game of Sabacc with real money).

2. Prerequisites

Tool Why you need it Command
Node ≥ 20 LTS = fewer headaches nvm install 20 && nvm use 20
pnpm / npm / yarn Pick one; bikeshedding optional corepack enable
Git Because ZIP files aren’t CI/CD sudo apt-get install git

3. create-next-app — the hyperdrive jump 🚀

pnpm create next-app@latest my-portfolio \
  --typescript \
  --eslint \
  --tailwind \
  --app \
  --src-dir \
  --import-alias "@/*"
cd my-portfolio && pnpm dev

--app gives you the App Router out of the box. If you still see a pages/ folder, you’re in the wrong timeline.


4. Jedi-Grade Folder Structure

src/
  app/
    layout.tsx
    page.tsx
    blog/
      page.tsx
      [slug]/
        page.tsx
  components/
  lib/
  styles/
content/
  blog/

Pro tip: Keep content outside src so the TypeScript compiler doesn’t try to parse your prose.


5. Installing Tailwind CSS

pnpm dlx tailwindcss init -p

Edit tailwind.config.js:

module.exports = {
  content: [
    "./src/app/**/*.{ts,tsx}",
    "./src/components/**/*.{ts,tsx}",
    "./content/**/*.{md,mdx}",
  ],
  theme: { extend: {} },
};

6. Creating Your First Component

'use client';
import Link from "next/link";
import { usePathname } from "next/navigation";

export default function Navbar() {
  const path = usePathname();
  const link = (href: string, label: string) => (
    <Link
      href={href}
      className={`px-3 py-2 ${
        path === href ? "text-yellow-300 border-b-2 border-green-500" : "text-green-300"
      } hover:text-yellow-300`}
    >
      {label}
    </Link>
  );
  return (
    <nav className="bg-[#121212] bg-opacity-90 backdrop-blur-md sticky top-0 z-50">
      <div className="mx-auto flex gap-4">
        {["/", "Home"], ["/about", "About"], ["/blog", "Blog"], ["/projects", "Projects"], ["/contacts", "Contact"]].map(([href, label]) => link(href, label))
      </div>
    </nav>
  );
}

7. Markdown Blog Setup

pnpm add gray-matter remark remark-html
// src/lib/posts.ts
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";

const postsDir = path.join(process.cwd(), "content", "blog");

export function getPosts() {
  return fs
    .readdirSync(postsDir)
    .filter((f) => f.endsWith(".md"))
    .map((file) => {
      const raw = fs.readFileSync(path.join(postsDir, file), "utf8");
      const { data, content } = matter(raw);
      return { slug: file.replace(/\.md$/, ""), ...data, content };
    })
    .sort((a, b) => b.date.localeCompare(a.date));
}

Render it:

// src/app/blog/page.tsx
import { getPosts } from "@/lib/posts";
import Link from "next/link";

export default function Blog() {
  const posts = getPosts();
  return (
    <section className="grid gap-6 md:grid-cols-2 py-10">
      {posts.map((p) => (
        <article
          key={p.slug}
          className="border border-green-500/50 p-6 rounded-xl"
        >
          <h2 className="text-2xl text-green-400 mb-1">{p.title}</h2>
          <p className="text-sm text-gray-400">
            {new Date(p.date).toDateString()}
          </p>
          <p className="my-4">{p.excerpt}</p>
          <Link href={`/blog/${p.slug}`} className="text-yellow-300">
            Read →
          </Link>
        </article>
      ))}
    </section>
  );
}

8. Deployment

  1. Push to GitHub.
  2. Connect to Vercel.
  3. Click Deploy.
  4. Profit.

9. Common Pitfalls (and How to Face-Palm Gracefully)

Mistake Symptoms Fix
Using Client Components everywhere Waterfall network tab, hot laptop Default to Server Components; sprinkle use client sparingly.
Forgetting viewport meta Mobile looks like an Imperial Star Destroyer console <meta name="viewport" content="width=device-width,initial-scale=1" />
Hard-coding URLs 404s in prod Use next/config or environment variables.
Pushing .env.local Tears, revoked tokens Add it to .gitignore, rookie.

10. Next Steps

  • Add MDX support.
  • Animate with Framer Motion.
  • Connect a headless CMS like Sanity.io.
  • Write tests.

Final Thought

"The ability to deploy an app is insignificant next to the power of a well-structured codebase."
Not Darth Vader, but he would’ve said it if he wrote React.

Happy coding, and may your builds be green! 🚀

Last updated: 2025-04-26