Skip to content

Editing the content

All the words and images of the site live in src/content/. You edit plain TypeScript files — quotes around text, commas between items — and the site updates while npm run dev is running. No CMS is needed. (If you would rather manage content in Sanity, Payload, Strapi or Directus, see Using a headless CMS.)

src/content/
site.ts name, description, domain, navigation, contact details, footer, social links
pages/home.ts the copy of the home page
pages/about.ts …and of every other page
collections/*.ts services, projects, posts, careers, team, testimonials
routes.ts the URL of every page
types.ts the fields each kind of item has

src/content/site.ts holds what appears on every page:

Field Shown where
site.name Logo alt text, browser title suffix, share cards
site.title Browser title of the home page
site.description Search results and share cards, for pages without their own description
site.url Set this to your domain before publishing. Share links, sitemap.xml and robots.txt are built from it
navigation The menu in the header
contact Email, phone, address, map link, opening hours — used on the Contact page and in the footer
footer Footer columns and social links

Links use routes (routes.about, routes.post("my-slug")) so a URL is written once and stays in sync.

One file per page in src/content/pages/. Each exports an object with the sections of that page and a meta entry:

export const about = {
meta: {
title: "About", // browser tab: "About | Nitip"
description: "We don't just market…", // search results and share cards
},
hero: { … },
values: { … },
};

Change the text between the quotes, keeping the field names — the sections read them by name. Apostrophes are safe inside double quotes, and the demo uses the typographic ’. A double quote inside a double-quoted string needs a backslash before it.

src/content/collections/ holds the lists the site builds pages from:

File What it feeds
services.ts The Services page and one detail page per service
projects.ts The Projects gallery and one case-study page per project
posts.ts The Blog and one page per post
careers.ts The Careers list and one page per role
team.ts The team grid on About
testimonials.ts The quotes slider on the home page

The order of the items in the file is the order on the site. The home page shows the first few of each list; the numbers are in src/app/page.tsx (services.slice(0, 5), projects.slice(0, 4)).

Copy an existing entry, paste it below, and change the values. A project, for example:

{
slug: "acme-brand-refresh", // the URL: /projects/acme-brand-refresh
title: "Acme Brand Refresh",
year: "2025",
image: "/images/project-acme.jpg", // a file in public/images
services: [{ label: "Branding & Identity Design", slug: "branding-identity-design" }],
summary: "One or two sentences shown in the gallery.",
industry: "Retail",
overview: { text: "", image: "/images/project-acme-overview.jpg" },
process: { text: "", images: ["/images/project-acme-process-1.jpg", "/images/project-acme-process-2.jpg"] },
result: { … },
}

slug must be unique and URL-safe (lower case, words joined by -). The page, the links to it and its entry in sitemap.xml appear by themselves — there is nothing else to register.

The services on a project link to the service pages, so their slug has to match a service in services.ts.

Delete the entry. If another item refers to it (a project pointing at a service you removed), your editor and npm run build will say so.

src/content/types.ts is the reference: it lists every field, which are optional (?), and a comment for the ones that are not obvious. Your editor shows the same information while you type.

A post body is a list of blocks, so the blog can render headings, paragraphs and lists consistently:

body: [
{ type: "heading", text: "Why prototyping matters", bold: "prototyping" },
{ type: "paragraph", text: "A paragraph of text.", highlight: "paragraph" },
{
type: "list",
ordered: true, // leave out for bullets
items: [
[{ type: "paragraph", text: "First item." }],
[{ type: "paragraph", text: "Second item, which can hold its own blocks." }],
],
},
]

bold and highlight are separate runs of bold text, not markers inside text: a heading prints text and then bold after it (text: "1. ", bold: "Start with a sketch"), a paragraph prints highlight in bold before text (highlight: "Instant traffic: ", text: "drive qualified visitors…"). Mind the trailing space.

  1. Put the file in public/images/.
  2. Refer to it in the content as /images/<file> (the path starts at public).

Sizes and formats are handled for you: pages use next/image, which serves a scaled, modern format per device. Use photos at least as wide as they appear (hero and project images: 2000 px or more, cards: 1200 px, avatars: 400 px), in .jpg for photos and .png when you need transparency. Keep the demo’s aspect ratios (the layout crops to fit).

Two images sit outside the content because browsers and social networks ask for them by name:

File What it is
src/app/icon.svg, favicon.ico, apple-icon.png The icon in the browser tab and on a phone’s home screen
src/app/opengraph-image.png The picture shown when someone shares a link (1200 × 630 or larger)

Describe the share image in src/app/opengraph-image.alt.txt.

Interface words that never change per site — “Load More”, “Read More”, form labels, error messages — live with the components in src/design-system/. Search for the phrase and you will find it. Everything a visitor reads as your words is in src/content/.

Terminal window
npm run build

A successful build means every page, link and image still resolves. See Going live for the rest of the checklist.