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 requiredYour details first
Section titled “Your details first”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.
Page copy
Section titled “Page copy”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.
Collections
Section titled “Collections”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)).
Adding an item
Section titled “Adding an item”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.
Removing an item
Section titled “Removing an item”Delete the entry. If another item still refers to it, npm run check and the build will say so.
What each field means
Section titled “What each field means”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.
Post bodies
Section titled “Post bodies”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.
Images
Section titled “Images”- Put the file in
src/assets/images/. - 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) |
Text that is not in content/
Section titled “Text that is not in content/”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/.
After editing
Section titled “After editing”npm run check && npm run buildTogether 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.