Prerequisites
You need Node.js 18 or later installed. Check your version with node -v. If you need to install it, download it from nodejs.org — the LTS release is the right choice for most people.
Create the project
The official scaffolding tool sets everything up in one command:
npx create-next-app@latest my-siteWhen prompted: TypeScript — yes, ESLint — yes, Tailwind CSS — yes, src/ directory — no, App Router — yes. Start the dev server:
cd my-site
npm run devYour site runs at http://localhost:3000. Open app/page.tsx and replace the default content with something minimal to confirm everything works:
export default function Home() {
return (
<main className="flex min-h-screen items-center justify-center">
<h1 className="text-4xl font-bold">Hello, world.</h1>
</main>
);
}Save the file and the browser updates instantly. That's a React Server Component — it runs entirely on the server, sends pure HTML to the browser, and ships zero client-side JavaScript. This is the default for every component in the App Router.
How routing works
The app/ directory is your entire routing structure. Every folder becomes a URL segment. A file named page.tsx inside that folder renders the page. A file named layout.tsx wraps every page in that folder and its children.
app/
layout.tsx → wraps every page (nav, footer)
page.tsx → /
about/
page.tsx → /about
services/
page.tsx → /services
contact/
page.tsx → /contactThe root layout.tsx is where you place your navigation and footer — components that appear on every page. They render once, wrap all child pages, and don't re-render between navigations.
Data-driven content
One of the most practical patterns in Next.js is separating content from presentation. Instead of hard-coding text into components, you define your content in TypeScript data files and import them wherever needed. The same data can drive multiple places on the site simultaneously.
A typical content data file looks like this:
// content/data/services.ts
export const SERVICES = [
{
id: "1",
title: "End User Support",
summary: "Responsive support for your team.",
items: ["Help desk", "Device setup", "Onboarding"],
},
{
id: "2",
title: "Server and Network",
summary: "Infrastructure that stays up.",
items: ["Monitoring", "Patch management", "Backups"],
},
];This single array can power a tabbed services page, a summary section on the home page, a footer list of offerings, and a sitemap entry — all reading from the same source. Update the data once and every instance reflects the change.
The same pattern works for news articles, team members, office locations, FAQ entries — any structured content that appears in more than one place. TypeScript gives you type safety across every component that imports the data, so if you rename a field the compiler catches every usage immediately.
Dynamic routing for content pages
When you have multiple items of the same type — job listings, blog posts, product pages — dynamic routes let you create one template that serves all of them. A folder named with square brackets becomes a dynamic segment:
app/
careers/
page.tsx → /careers (listings)
[slug]/
page.tsx → /careers/senior-engineer (individual)Inside [slug]/page.tsx, the slug value from the URL is available as a prop. You use it to look up the matching content — from a data file, a CMS, or a folder of MDX files.
In practice this looks like: a content/jobs/ folder with one .mdx file per job. The slug matches the filename. The [slug]/page.tsx reads the matching file, renders it, and Next.js handles the routing. Add a new .mdx file and a new page exists automatically — no code changes required.
MDX — markdown with components
MDX is Markdown that can import and use React components. It's the right tool for content that is mostly text but occasionally needs something richer — a callout box, a code block with syntax highlighting, an embedded table.
Common uses: privacy policies, terms and conditions, blog posts, documentation, job descriptions. The content lives in .mdx files that non-developers can edit. The presentation is handled by the React component that imports it. They stay cleanly separated.
// app/privacy/page.tsx
import Policy from "./policy.mdx";
export default function PrivacyPage() {
return (
<main className="prose mx-auto py-24 px-6">
<Policy />
</main>
);
}The policy.mdx file is plain Markdown. Update it without touching any component code. The page renders it with whatever styling the prose class applies from Tailwind Typography.
Articles with internal and external links
A news or articles section is a good example of where data-driven content and dynamic routing work together. The data file holds an array of article objects — each with a title, summary, image, and a URL. That URL can be an external link to a third-party article, or an internal path to a page you've built.
export const ARTICLES = [
{
id: "1",
url: "/articles/building-with-nextjs", // internal
title: "Building a real website with Next.js",
summary: "...",
image: { url: "/images/articles/nextjs.png", ... },
},
{
id: "2",
url: "https://example.com/external-article", // external
title: "An external reference",
summary: "...",
image: { url: "/images/articles/external.png", ... },
},
];The news section component maps over this array and renders a card for each article. Internal links use Next.js <Link> for client-side navigation. External links open in a new tab. The card component doesn't care — it just renders what the data tells it to. When you write a new article, create the page at the matching path and add an entry to the array. It appears in the news section automatically.
Client components — adding interactivity
Everything in the App Router is a Server Component by default. To add interactivity — state, event handlers, browser APIs — you opt into a Client Component with the "use client" directive at the top of the file:
"use client";
import { useState } from "react";
export function FAQItem({ question, answer }) {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(!open)}>{question}</button>
{open && <p>{answer}</p>}
</div>
);
}The key discipline is keeping client components small and at the leaves of the component tree. A page can be a Server Component that fetches data and passes it to a small Client Component that handles the accordion toggle. The data fetching stays on the server. The interactivity stays in the browser. Neither bleeds into the other.
SEO — sitemap and robots
Next.js can generate a sitemap and robots.txt automatically by creating special files in the app/ directory. These are TypeScript files that export a function — Next.js calls the function at build time and serves the result at the correct URL.
// app/sitemap.ts
export default function sitemap() {
return [
{ url: "https://example.com/", lastModified: new Date() },
{ url: "https://example.com/services", lastModified: new Date() },
{ url: "https://example.com/contact", lastModified: new Date() },
];
}For a site with dynamic content — articles, job listings — you can generate sitemap entries programmatically by reading your data files inside the sitemap function. Every article in your data array gets a sitemap entry without any manual work.
Page-level metadata (title, description, Open Graph tags) is handled with a metadata export in each page.tsx. Next.js handles the HTML <head> entirely — no helmet libraries, no manual tag management.
// app/services/page.tsx
export const metadata = {
title: "Services",
description: "IT support and consulting services.",
};Putting it together
A well-structured Next.js site separates concerns cleanly: content lives in content/ as TypeScript data files or MDX documents, presentation lives in components/, pages live in app/. Changing copy means editing a data file. Adding a service means adding an object to an array. Adding a page means creating a folder and a file.
This is what makes the framework genuinely useful beyond simple demos — the architecture scales well as the site grows, and the clear separation makes it maintainable by someone who didn't write it.
Next steps
Once your site is built, the next question is hosting. For a content site like this, Cloudflare Workers via vinext is a practical choice — fast, cheap, and no infrastructure to manage. Read the deployment guide for the full walkthrough.
If you'd rather have someone handle the build and deployment for you, our Website Support service covers new builds, ongoing maintenance, and hosting configuration end to end.
