Using a headless CMS — Tustel Next.js
Connecting a headless CMS works the same for every Next.js template sold on Framebrick. The approach, rich text, images, keeping pages fresh, and step-by-step guides for Sanity, Payload, Strapi and Directus are in Use a headless CMS with a Next.js template. This page lists what is specific to Tustel.
Collections
Section titled “Collections”Pages read them through src/lib/content.ts: getProjects / getProject / getOtherProjects, getPosts /
getPost / getOtherPosts, getServices, getTestimonials and getClients. Mirror src/content/types.ts in the
CMS:
| Collection | Key fields |
|---|---|
Project |
slug, title, category, summary, image, client, year, timeline, industry, deliverables, scope, description (paragraphs), gallery (images), challenge (paragraphs), challengeGallery (images) |
Service |
slug, name, title, summary, image |
Testimonial |
slug, name, role, photo, quote |
Post |
slug, title, excerpt, image, category, date, body (rich text) |
Client |
name, logo |
- No collection refers to another. A project’s
categoryis free text; a post’s is one of a few words (the demo uses Photography, Development, Design and Branding), so a choice field in the CMS works. descriptionandchallengeare lists of plain paragraphs: a list of text fields, or a long text your mapping function splits at blank lines.galleryandchallengeGalleryare lists of images; each gallery puts the first half of its list in the left column and the rest in the right one.- Keep the collection’s order in the CMS (a sort field), because the site shows items in the order it receives them, not by date. Home shows the first six projects (four on tablets and phones), every service (numbered 01, 02, … in that order), every testimonial, the first three posts and every client; a project page lists the first six other projects, a post the first three other posts; Portfolio and Blog load six at a time on desktop and four on tablets and phones.
- Services, testimonials and clients have no pages: a service’s or a testimonial’s
slugis only an id. getOtherProjectsandgetOtherPostsfilter the full list, so they keep working whengetProjectsandgetPostsfetch from the CMS.
Rich text
Section titled “Rich text”Post bodies are blocks, not HTML:
{ type: "heading", text: "…" } // one heading size{ type: "paragraph", text: "…" } // *italic* between asterisks, "\n" for a line break{ type: "list", ordered?: true, items: [[ …blocks ], [ …blocks ]] } // each item is a list of blocksThere is no bold and there are no links inside the text. The demo’s posts use headings, paragraphs (with italics and
line breaks) and bulleted lists. Convert your CMS’s rich text to these blocks in your mapping function, italics as
*…* (how).
Images
Section titled “Images”Upload images at least as large as the layout uses: 2400 px wide for project covers and post photos, 1200 px for gallery photos, 800 px for service photos and 200 × 200 for testimonial portraits. Client logos are SVG files shown 25 px tall at their own width. Using a CMS’s images in the site: Images.
Project cards, the project cover, the galleries and the photo at the top of a post keep each photo’s own proportions.
For files in public/, src/lib/image-size.ts reads the size when the site builds; a CMS image is an address it can’t
read, so those boxes fall back to 4:3 (3:2 for a post photo, 100 × 25 for a client logo) and crop the photo to fit.
Every CMS stores an image’s width and height: return them from your mapping functions and let imageSize() answer with
them for CMS addresses. It is called from src/lib/to-ui.ts and from the project and post pages
(src/app/portfolio/[slug]/page.tsx, src/app/blog/[slug]/page.tsx).