Editing the content — Tustel Next.js
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, menu, contact details, footer, and the parts several pages share pages/home.ts the copy of the home page pages/about.ts …and of every other page collections/*.ts projects, services, testimonials, posts, clients routes.ts the URL of every page types.ts the fields each kind of item hasNot 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.ts holds what appears on every page, and the sections that several pages share:
| Export | Shown where |
|---|---|
site.name |
Browser title suffix (“About | Tustel”), share cards |
site.owner |
The logo’s label for screen readers, in the header and the footer |
site.title |
The browser title of the home page, and of any page without its own |
site.description |
Search results and share cards of the home page, and of 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) |
bookCall |
“Book a call”: the header button, and the first button in the Home hero, the photo band, “Let’s work together” and the About hero. It links to https://cal.com/: put your own booking page there (Cal.com, Calendly) or /contact. |
viewWorks |
“View my works”: the second button in the same places, linking to the Portfolio |
contact |
E-mail, phone, location and response time, with their labels, in “Let’s work together” (Home and the project pages) |
socials |
The social links in the Home hero, the About hero and the footer |
footer |
The note at the bottom of every page ({year} becomes the current year) |
workTogether |
“Let’s work together” on Home and the project pages: label, title, text and photo |
band |
The full-screen photo with a call to action on Home and About: title, text and photo |
journal |
“From the journal” on Home and under every post: label, title and the “View all articles” link |
facts |
The four counters on Home and About (“80+ Years Experience”, …) |
The demo’s e-mail (hello@example.com), phone number and location are placeholders: replace all of them. The phone’s
link is built from the number (+1 (415) 123-4567 calls tel:+14151234567), so write it the way people should read
it. The Contact page shows only the form; your e-mail and phone appear in “Let’s work together”.
Links that start with https:// (the booking link, the social links) open in a new tab; mailto: and tel: links open
the visitor’s mail app or phone.
A counter counts up by one every 5 ms from from to value when it scrolls into view, then shows suffix after it:
{ value: "120", from: "40", suffix: "+", label: "Projects Completed" }, // counts 40 → 120, shows "120+"Write whole numbers. Keep the distance between from and value to a few hundred (a counter to 5000 from 0 would
count for 25 seconds), and use suffix: "" for no suffix.
Your name appears in several places: site.owner, the hero name in pages/home.ts and pages/about.ts, the footer
note, the page descriptions (meta.description in about.ts, portfolio.ts, blog.ts and contact.ts), the
descriptions of the two “About me” photos and the testimonial quotes (“Working with Alex…”). The logo draws it too
(Making it look like your brand). Search src/content/ for “Alex” to find them all.
Page copy
Section titled “Page copy”There is one file per page in src/content/pages/. Most export an object with the sections of that page and a meta
entry:
export const about = { meta: { title: "About", // browser tab: "About | Tustel" description: "Alex Carter is a photographer and…", // search results and share cards (80–160 characters) }, hero: { … }, process: { … },};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 both the straight ’ and the typographic ’. A double quote inside a double-quoted string needs a backslash before it.
| File | Page |
|---|---|
home.ts |
Home: the hero (the subtitle, the greeting, your name, your role, the three slideshow photos, and the card with your portrait, its label and the five specialities that scroll by), the line above the client logos, “About me” (two paragraphs, the “More about me” link, two photos), the portfolio heading with the words “Catalog” and “List”, the services heading and text, and the testimonials heading with the arrows’ labels for screen readers. It has no meta: its title and description are site.title and site.description. |
about.ts |
About: the hero (greeting, name, portrait, role, introduction) and “How I work” (label, title, text, photo and the four steps) |
portfolio.ts |
Portfolio (“Works” in the menu): label, title, text, and the text screen readers hear while more projects load |
project.ts |
The words shared by every project page: the four labels under the title (Client, Category, Year, Timeline), “Project Description”, “Project Details” and its four labels, “The Challenge” and “Other Projects” |
blog.ts |
Blog: label, title, text, and the text screen readers hear while more posts load |
contact.ts |
Contact: label, title and text, and the contact form (contactForm): fields, the note, the button texts and the title of each submission (Forms) |
not-found.ts |
The 404 page: its browser title, “404”, the title, the text, the button and the photo |
A post page has no copy file of its own: it shows the post, then “From the journal” (journal in site.ts).
Collections
Section titled “Collections”src/content/collections/ holds the lists the site builds pages and sections from:
| File | What it feeds |
|---|---|
projects.ts |
The Portfolio page, one page per project, the portfolio on Home (the first six on desktop, the first four on tablets and phones) and “Other Projects” at the end of a project page (the first six other projects) |
services.ts |
“What I Do” on Home (all of them, numbered 01, 02, … in the file’s order) |
testimonials.ts |
The testimonial slideshow on Home (all of them) |
posts.ts |
The Blog, one page per post, “From the journal” on Home (the first three) and under a post (the first three other posts) |
clients.ts |
The logo ticker on Home (all of them) |
The order of the items in the file is the order on the site. The Blog doesn’t sort posts by date either: put a new post first to show it first. Portfolio and Blog show six items on desktop and four on tablets and phones, then more each time the visitor scrolls to the end of the list.
Adding a project
Section titled “Adding a project”Copy an existing entry, paste it below, and change the values:
{ slug: "lumen-studio", // the URL: /portfolio/lumen-studio title: "Lumen Studio", category: "Brand Photography", // any words: on the cards and under the title summary: "A photography series for…", // the List view on Home and the page's search description image: "/images/project-lumen-studio.jpg", // the cover: the cards and the wide photo under the title client: "Lumen Studio", year: "2026", timeline: "4 Weeks", // under the title and in "Project Details" industry: "Hospitality", deliverables: "Brand Photography, Art Direction", scope: "Creative Direction, Post-production", description: ["First paragraph.", "Second paragraph."], // "Project Description" gallery: ["/images/project-lumen-studio-1.jpg", …], // the photos after the details challenge: ["First paragraph.", "Second paragraph."], // "The Challenge" challengeGallery: ["/images/project-lumen-studio-5.jpg", …], // the photos after the challenge}A project page shows the title with the client, category, year and timeline under it, then the cover, “Project Description” with its paragraphs, “Project Details” (industry, deliverables, timeline and scope), the first gallery, “The Challenge”, the second gallery, “Other Projects” and “Let’s work together”. The paragraphs are plain text, one string each.
Each gallery has two columns: the first half of its photos on the left and the rest on the right (with three photos, two on the left and one on the right), every photo at its own proportions. The demo’s galleries have three or four photos.
Adding a post
Section titled “Adding a post”| Field | What it is |
|---|---|
slug |
The URL: /blog/<slug> |
title |
The card and the top of the post |
excerpt |
Under the title on the post page, and the search description |
image |
The card (cropped to about 1.41 : 1) and the photo under the title, shown whole up to one screen tall |
category |
Any words, shown on the card and above the title. The demo uses Photography, Development, Design and Branding. |
date |
"2025-04-16", shown as “Apr 16, 2025” |
body |
The text, as rich text (below) |
A project’s search description is its summary, cut at 160 characters. A post’s is its excerpt; an excerpt shorter than 80 characters is followed by the start of the post’s first paragraph, cut at 160.
Adding a service, testimonial or client
Section titled “Adding a service, testimonial or client”| Add | Fields |
|---|---|
| Service | slug (an id: services have no page), name (the numbered row, “01 — Photography”), title and summary (shown for the chosen service) and image (shown 300 × 200) |
| Testimonial | slug (an id), name, role ("Founder, Aurora Coffee"), photo (round, 68 px) and quote |
| Client | name (also the logo’s text for screen readers) and logo: an SVG file in public/images/ with a viewBox or a width and height, shown 25 px tall at its own proportions |
On Home, pointing at a service on desktop (or tapping it on a tablet) shows its title, text and photo beside the list; on phones the chosen service opens under its name.
Removing an item
Section titled “Removing an item”Delete the entry. Its page, its card and its sitemap entry disappear on the next build; no other item refers to it. Home leaves out its services list when there are no services, and its portfolio section when there are no projects.
What each field means
Section titled “What each field means”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.
Rich text: posts
Section titled “Rich text: posts”A post body is a list of blocks:
body: [ { type: "heading", text: "1. Aligning on Purpose and Personality" }, { type: "paragraph", text: "A plain paragraph with an *italic* word." }, { type: "paragraph", text: "The mistake:\nOvercomplicated menus leave users frustrated." }, // \n: a line break { type: "list", items: [ // bullets; ordered: true for numbers [{ type: "paragraph", text: "First item" }], [{ type: "paragraph", text: "Second item" }], ] },]Headings have one size. Text is plain apart from two marks: words between asterisks are set in italics, and \n starts
a new line inside a paragraph. There is no bold and there are no links inside the text. Each list item is itself a list
of blocks, so an item can hold a nested list. The demo’s posts use headings, paragraphs and bulleted lists.
Images
Section titled “Images”- Put the file in
public/images/. - Refer to it in the content as
/images/<file>(the path starts atpublic).
Sizes and formats are handled for you: pages use next/image, which serves a scaled, modern format per device. Use
photos at least as large as the demo’s, which the placeholders show:
| Photos | Demo size |
|---|---|
| The Home hero slideshow | 2400 px wide, landscape (they fill the screen, cropped) |
| The photo band (Home, About) and the 404 photo | 2400 × 3600, portrait (they fill the screen, cropped) |
| Project covers | 2400 px wide, any proportion |
| Project gallery photos | 1200 px wide, any proportion |
| Post photos | 2400 px wide |
| “Let’s work together” (Home, project pages) | 1200 × 1600, portrait |
| “How I work” on About | 1200 × 1800, portrait |
| “About me” on Home | 1000 × 750 and 600 × 399 |
| Service photos | 800 px wide (shown 300 × 200) |
| Your portrait (the Home hero card, the About hero) | 300 × 300 |
| Testimonial portraits | 200 × 200 |
Photos in a fixed frame are cropped to fill it. Project covers, gallery photos and the photo at the top of a post are
not: src/lib/image-size.ts reads the size of each file in public/ when the site builds, so the project cards, the
cover, the galleries and the post photo keep the photo’s own proportions and keep their space while it loads. Replacing
one of these photos with one of another shape changes the height of its card or its place in the gallery: a portrait
cover makes a tall card, and a row of cards lines up at the top. The cover on a project page and the photo of a post are
at most one screen tall (cropped beyond). An image from another address (a CMS or a CDN) can’t be measured this way: its
box falls back to 4:3 (3:2 for a post photo) unless you pass its size (Using a headless CMS).
Photos in the page copy written as { src, alt } have an alt: a short description for screen readers and search
engines. Write one for each photo you change. A photo used in two places changes in both when you replace the file (your
portrait is in the Home hero card and the About hero); to show different photos, give one of them another path in the
content.
Two things sit outside the content because browsers and social networks ask for them by name:
| File | What it is |
|---|---|
src/app/icon.svg, src/app/apple-icon.png |
The icon in the browser tab (it turns white in a dark browser theme) and on a phone’s home screen (180 × 180) |
public/opengraph-image.png |
The picture shown when someone shares a link (2400 × 1260; 1200 × 630 or larger) |
The share image’s description for screen readers is shareImage.alt in src/lib/meta.ts, next to its size: change
width and height there if your image has another size. Every page uses this image when shared, except the project
and post pages, which share their cover or photo.
Text that is not in content/
Section titled “Text that is not in content/”Almost every word is content, including the form’s messages (contactForm in pages/contact.ts). The few interface
words that never change per site live in the code:
- “Main” (the menu’s name for screen readers) and “Menu” (the phone menu button’s label) in
src/components/site/Header.tsx; - the “/” between “Catalog” and “List” in
src/components/sections/PortfolioSection.tsx, and the “ — “ between a service’s number and its name insrc/components/sections/ServicesSection.tsx; - the dash between a post card’s date and category in
src/design-system/components/cards/PostCard.tsx; - the date format of posts (“Apr 16, 2025”) in
src/lib/format.ts.
Search for the word and you will find it.
After editing
Section titled “After editing”npm run buildA successful build means every content file has the fields it needs and every page still renders. It doesn’t check that
the images exist or that links written as text ("/contact") lead somewhere: click through the site once with
npm start after big changes. See Going live for the rest of the checklist.