Editing the content — Titen Astro
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, menu, header button, contact details, footer, the project form pages/home.json the copy of the home page pages/about.json …and of every other page (About also holds the team and the awards) collections/*.json project categories, projects, services, posts, testimonials schemas.ts the fields each kind of item has, and which are requiredNot sure which file holds a piece of text? The page maps label every section of every page with its file.
Your details first
Section titled “Your details first”src/content/site.json holds what appears on every page:
| Field | Shown where |
|---|---|
site.name |
Logo label, 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. Canonical links, share links, sitemap.xml and robots.txt are built from it. |
navigation |
The menu in the header (and the phone menu) |
headerAction |
The button on the right of the header (“Contact us”) |
search |
The “Search projects” label and the search field’s placeholder |
contact |
E-mail, phone and address: used in the footer and on the Contact page |
socials |
The social links at the bottom of the footer |
footer |
“Let’s Talk!” with its text and button, the tagline, the “Mail us” / “Call us” labels, the Studio column, the newsletter, the copyright line ({year} becomes the current year) |
projectForm |
“Let’s Work Together” at the end of Projects, the category pages and every service page: title, text, image, fields, button and messages. The Contact page uses the same fields. |
site.url is read by astro.config.mjs at build time, so restart npm run dev after changing it.
The demo’s e-mail (contact@example.com), phone number and address are placeholders: replace all three. The footer’s
Work column lists the project categories by itself.
Page copy
Section titled “Page copy”There is one file per page in src/content/pages/. Each holds the sections of that page and, for pages with a fixed
URL, a meta entry:
{ "meta": { "title": "About", "description": "Titen is an architecture and construction studio…" }, "hero": { "…": "…" }}title becomes the browser tab (“About | Titen”), and description the text in search results and share cards (80–160
characters). 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.
| File | Page |
|---|---|
home.json |
Home. It has no meta: its title and description are site.title and site.description. |
about.json |
About, including the six team members (team.members) and the five awards (awards.items) |
projects.json, services.json, contact.json, testimonials.json |
The pages of the same name |
blog.json |
The Blog page (blogPage) and the words shared by every post (postPage: “Related”, “Similar Posts”) |
category.json |
The title and description of the category pages; {category} becomes the category’s name |
project.json, service.json |
The words shared by every project and service page (labels, “Other Projects”, the brochure button) |
not-found.json |
The 404 page |
Collections
Section titled “Collections”src/content/collections/ holds the lists the site builds pages from:
| File | What it feeds |
|---|---|
categories.json |
One page per category (/residential, /hospitality, /commercial), the buttons on Projects and the footer’s Work column |
projects.json |
The Projects page, one page per project, the search in the header, Home (the first seven), its category’s page, “Other Projects” at the end of a project (up to three more of the same category), and the small photos on Services and Contact (the first six project images) |
services.json |
The Services page, one page per service, and the expanding list on Home and Testimonials (the first four) |
posts.json |
The Blog, one page per post, Home (the first three) and “Similar Posts” at the end of a post (up to three of the same category) |
testimonials.json |
The Testimonials page and the slideshow in the Home hero (the first five) |
The order of the items in the file is the order on the site.
Adding a project
Section titled “Adding a project”Copy an existing entry, paste it after the last one (mind the comma between entries), and change the values:
{ "slug": "harbour-library", "title": "Harbour Library", "location": "Oslo, Norway", "year": "2026", "category": "commercial", "image": "/images/project-harbour-library.jpg", "summary": "A public library on the water…", "intro": { "text": "…", "image": "/images/project-harbour-library-intro.jpg", "gallery": ["/images/…-gallery-1.jpg", "/images/…-gallery-2.jpg", "/images/…-gallery-3.jpg"] }, "story": { "image": "/images/…-story.jpg", "text": "…", "wide": "/images/…-wide.jpg" }, "details": { "texts": ["…", "…"], "gallery": ["/images/…", "/images/…", "/images/…"] }, "closing": [{ "text": "…", "image": "/images/…-closing.jpg" }]}Here’s what each field does:
slugis the URL:/projects/harbour-library.categoryis the slug of a category incategories.json.imageis used on the cards and at the top of the project page (about 1920 × 1280).summaryappears on the cards and at the top of the page, and is the search description.intro.textsits next to the details (location, category, year);intro.imageis a portrait of about 800 × 1100.closinghas one part or two.
A project page has four parts (five when closing has two entries). A text can hold several paragraphs: separate them
with an empty line (\n\n).
Adding a service
Section titled “Adding a service”{ "slug": "acoustic-design", "number": "09", "title": "Acoustic Design", "overview": "Spaces that sound as good as…", "image": "/images/service-acoustic-design.jpg", "points": ["…", "…", "…", "…", "…"], "about": "…", "included": []}slugis the URL:/services/acoustic-design.numberis shown on the Services page.overviewappears in the service lists and is the search description.imageis about 1400 × 1050.pointsis “Key Point”: five short lines.aboutis “About the service” on its page.includedis “What’s included”, in rich text (see below).
Adding a category, post or testimonial
Section titled “Adding a category, post or testimonial”| Add | Fields |
|---|---|
| Category | slug (the URL, /<slug>) and title. Then set category on its projects. Pick a slug that isn’t already a page (about, blog, contact, …). |
| Post | slug, title, excerpt (the card text), image (about 1920 × 1280), category (any words; posts with the same one are listed under each other as “Similar Posts”), author, date ("2024-03-12"), body (rich text) |
| Testimonial | slug, quote, name, photo (round, about 96 px), and rating (out of 5, e.g. 4.8; leave it out to show the star alone) |
A post’s search description is its title followed by its excerpt (cut at 160 characters).
Team and awards
Section titled “Team and awards”The team and the awards appear only on About, so they live in src/content/pages/about.json:
team.members:name,role,bio,photo(portrait, about 800 × 1000) andlinkedin.awards.items:title,award(the name in orange),descriptionandyear.
Removing an item
Section titled “Removing an item”Delete the entry (and the comma before it, if it was the last one). Its page, its card and its sitemap entry disappear
on the next build. A project names its category by slug: if you delete a category, give its projects another one.
What each field means
Section titled “What each field means”src/content/schemas.ts is the reference: it lists every field, which are optional (.optional()), and a comment for
the ones that are not obvious. npm run check reports a missing or misspelled field with the file and the line.
Rich text: posts and “What’s included”
Section titled “Rich text: posts and “What’s included””A post body, or a service’s included, is a list of blocks:
[ { "type": "heading", "text": "Why natural light matters" }, { "type": "paragraph", "text": "A plain paragraph." }, { "type": "paragraph", "highlight": "Bold start:", "text": " and the rest of the paragraph." }, { "type": "list", "items": [ [{ "type": "paragraph", "text": "First item" }], [{ "type": "paragraph", "highlight": "Second:", "text": " with a bold start" }] ] }]Each list item is itself a list of blocks, so an item can hold paragraphs and a nested list; add "ordered": true for
numbers. Text is plain: the bold parts are highlight, not **stars**.
Videos
Section titled “Videos”Two videos play on the site. Each has a src (a web address or a file) and a poster (the picture shown before it
plays):
| Video | Where it plays | Field |
|---|---|---|
| Hero video | Home, behind the title, under a dark cover | hero.video in src/content/pages/home.json |
| Studio video | About, between the approach slideshow and the team | video in src/content/pages/about.json |
The demo plays stock clips, which are not part of the download: the About clip plays from the web, and the hero’s
public/videos/home-hero.mp4 in the download is a short placeholder. To use your own, put an MP4 in
public/videos/ and set "src": "/videos/hero.mp4". Keep a wide format (16:9), export at about 1280–1920 px wide, and
keep each file small (a few MB): they play muted and in a loop. Replace the matching poster in src/assets/images/ too.
For visitors who prefer reduced motion, only the poster shows.
The demo’s hero clip is a free stock video from Magnific, whose license asks for a credit: “Designed by Magnific”,
shown small at the bottom right of the hero (hero.video.credit in home.json). Remove it when you use your own video.
The brochure
Section titled “The brochure”“Download our brochure” (Home) and “Download Brochure” (every service page) download public/brochure.pdf. The file in
the download is a placeholder page: replace it with your own PDF under the same name, or change the href in
src/content/pages/home.json and service.json.
Images
Section titled “Images”- Put the file in
src/assets/images/. - Refer to it in the content as
/images/<file>.
Astro resizes, compresses and converts the images when the site builds, so each device gets a size that fits. Use photos at least as wide as they appear: wide photos 1920 px, project photos 1000–1920 px, service photos 1400 px, portraits 800 px. Keep the demo’s aspect ratios, because the layout crops to fit. A content path that points to a missing file stops the build with its name.
Files that browsers and social networks ask for by name live in public/:
| File | What it is |
|---|---|
public/icon.svg, public/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 (2400 × 1260; 1200 × 630 or larger) |
Its description for screen readers is set in src/layouts/Layout.astro. Every page uses this image when shared, except
the project, service and post pages, which share their own image.
Text that is not in content/
Section titled “Text that is not in content/”Almost every word is content, including the forms’ messages (projectForm and footer.newsletter in site.json). The
few interface words that never change per site live in the code:
- “Menu” (the phone menu button’s label for screen readers) and “Clear” (the search field) in
src/design-system/; - “Testimonials” (the name of the Home slideshow for screen readers) in
src/components/sections/HomeHeroSection.astro.
Search for the word and you will find it.
After editing
Section titled “After editing”npm run buildA successful build means every page, link and image still resolves. See Going live for the rest of the checklist.