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
- Push to GitHub.
- Connect to Vercel.
- Click Deploy.
- 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