Skip to content

Editing the content

All the words and images of the site live in src/content/. You edit plain JSON files — text in quotes, commas between entries — 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.json name, description, domain, navigation, contact details, footer, social links
pages/home.json the copy of the home page
pages/about.json …and of every other page
collections/*.json services, projects, posts, careers, team, testimonials
schemas.ts the fields each kind of item has, and which are required

src/content/site.json 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, canonical 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

site.url is read by astro.config.mjs at build time, so restart npm run dev after changing it.

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

{
"meta": {
"title": "About",
"description": "We don't just market — we create meaningful connections that drive growth."
},
"hero": { "…": "" }
}

title becomes the browser tab (“About | Nitip”) and description the text in search results and share cards. Change what is inside the quotes and keep the field names — the sections read them by name. JSON has no comments and no trailing commas; if the site stops building, that is usually why.

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

File What it feeds
services.json The Services page and one detail page per service
projects.json The Projects gallery and one case-study page per project
posts.json The Blog and one page per post
careers.json The Careers list and one page per role
team.json The team grid on About
testimonials.json 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/pages/index.astro (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",
"title": "Acme Brand Refresh",
"year": "2025",
"image": "/images/project-acme.jpg",
"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" }
}

slug must be unique and URL-safe (lower case, words joined by -); it becomes the address /projects/acme-brand-refresh. The page, the links to it and its entry in sitemap.xml appear by themselves.

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

Delete the entry. If another item still refers to it, npm run check and the build will say so.

src/content/schemas.ts is the reference: every field, its type, and .optional() for the ones you can leave out. The build validates your JSON against it and names the file, the item and the field when something is missing.

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

"body": [
{ "type": "heading", "text": "1. ", "bold": "Start with a sketch" },
{ "type": "paragraph", "highlight": "Instant traffic: ", "text": "drive qualified visitors from day one." },
{
"type": "list",
"ordered": true,
"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, a paragraph prints highlight in bold before text. Mind the trailing space.

  1. Put the file in src/assets/images/.
  2. Refer to it in the content as /images/<file>.

Astro resizes each photo, converts it to a modern format and sets the width and height for you, so the page never jumps while loading. Use photos at least as wide as they appear (hero and project images: 2000 px or more, cards: 1200 px, avatars: 400 px), .jpg for photos and .png when you need transparency. Keep the demo’s aspect ratios — the layout crops to fit.

Files in public/ are served untouched, which is what the icons, the share image and the interface icons need:

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

Interface words that never change per site — “Load More”, “Read More”, form errors — 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 check && npm run build

Together they confirm every content file matches its schema and every page, link and image still resolves. See Going live for the rest of the checklist.