Skip to content
Sign inJoin waitlist

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 required

Not sure which file holds a piece of text? The page maps label every section of every page with its file.

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.

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

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.

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:

  • slug is the URL: /projects/harbour-library.
  • category is the slug of a category in categories.json.
  • image is used on the cards and at the top of the project page (about 1920 × 1280).
  • summary appears on the cards and at the top of the page, and is the search description.
  • intro.text sits next to the details (location, category, year); intro.image is a portrait of about 800 × 1100.
  • closing has 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).

{
"slug": "acoustic-design",
"number": "09",
"title": "Acoustic Design",
"overview": "Spaces that sound as good as…",
"image": "/images/service-acoustic-design.jpg",
"points": ["…", "…", "…", "…", "…"],
"about": "…",
"included": []
}
  • slug is the URL: /services/acoustic-design.
  • number is shown on the Services page.
  • overview appears in the service lists and is the search description.
  • image is about 1400 × 1050.
  • points is “Key Point”: five short lines.
  • about is “About the service” on its page.
  • included is “What’s included”, in rich text (see below).
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).

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) and linkedin.
  • awards.items: title, award (the name in orange), description and year.

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.

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**.

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.

“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.

  1. Put the file in src/assets/images/.
  2. 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.

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.

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.