Skip to content

CodeWizard-AB/Gcralliance-Studio

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Gcralliance-Studio

Content Management Studio for the Global Cardiovascular Research Alliance website Built with Sanity v5 β€” the structured content platform powering all editorial content across the GCRA website.


Table of Contents


Overview

This repository is the Sanity Studio for the Global Cardiovascular Research Alliance (GCRA) website. It is a standalone content management interface that runs inside the Next.js frontend at /studio β€” meaning editors access it directly on the live website domain without needing a separate login or URL.

The Studio gives non-technical team members full control over all website content β€” research publications, events, blog posts, ambassador profiles, team members, and page-level content β€” through a structured, user-friendly editing interface. No developer is needed for routine content updates.

The frontend website (separate repository) consumes all content from this Studio via the Sanity Content Lake using GROQ queries over the Sanity CDN API.


What This Studio Manages

Content Type Description URL on Website
Home Page Hero, stats strip, mission section, CTA banners /
About Page Mission, vision, values, history timeline, partners /about
Membership Page Tiers, training modules, testimonials, FAQ /membership
Research Publications Peer-reviewed papers, abstracts, PDFs, authors /research, /research/[slug]
Events Conferences, workshops, webinars, registration links /events, /events/[slug]
Blog Posts Articles, research spotlights, community news /blog, /blog/[slug]
Ambassadors Country ambassadors with map coordinates /ambassadors
Team Members Leadership and staff profiles /about
Authors Blog post author profiles /blog/[slug]
Categories Taxonomy for blog and research Filters on /blog, /research
Site Settings Org name, contact details, social links, SEO defaults Global (all pages)
Navigation Navbar links, footer columns, CTA button Global (all pages)

Project Structure

Gcralliance-Studio/
β”‚
β”œβ”€β”€ sanity.config.ts              # Root Studio configuration β€” registers all schemas,
β”‚                                 # plugins, and desk structure
β”‚
β”œβ”€β”€ sanity/
β”‚   β”œβ”€β”€ structure/
β”‚   β”‚   └── index.ts              # Custom Studio sidebar navigation structure
β”‚   β”‚
β”‚   β”œβ”€β”€ schemas/
β”‚   β”‚   β”œβ”€β”€ _shared.ts            # Shared field helpers and reusable object types
β”‚   β”‚   β”‚                         # (CTA, SEO, StatItem, Testimonial, FAQ, etc.)
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ documents/
β”‚   β”‚   β”‚   β”œβ”€β”€ index.ts          # All document schemas:
β”‚   β”‚   β”‚   β”‚                     # post, research, event, ambassador,
β”‚   β”‚   β”‚   β”‚                     # teamMember, category, author
β”‚   β”‚   β”‚   └── singletons.ts     # Singleton page schemas:
β”‚   β”‚   β”‚                         # homePage, aboutPage, membershipPage,
β”‚   β”‚   β”‚                         # siteSettings, navigationSettings
β”‚   β”‚   β”‚
β”‚   β”‚   └── objects/              # (Optional) standalone object schema files
β”‚   β”‚
β”‚   └── lib/
β”‚       └── fetch.ts              # Server-side Sanity fetch helper with cache tags
β”‚
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ studio/
β”‚   β”‚   β”‚   └── [[...tool]]/
β”‚   β”‚   β”‚       └── page.tsx      # Next.js route that serves the Studio at /studio
β”‚   β”‚   β”‚
β”‚   β”‚   └── api/
β”‚   β”‚       └── sanity/
β”‚   β”‚           └── revalidate/
β”‚   β”‚               └── route.ts  # Webhook endpoint for ISR cache revalidation
β”‚   β”‚
β”‚   └── lib/
β”‚       └── sanity/
β”‚           └── index.ts          # Sanity client instances + all GROQ queries
β”‚                                 # consumed by the Next.js frontend
β”‚
β”œβ”€β”€ .env.example                  # Template for required environment variables
β”œβ”€β”€ .env.local                    # Local secrets β€” git-ignored, never commit
└── package.json

Prerequisites

Before setting up, ensure you have:

  • Node.js v18.17.0 or later β€” check with node --version
  • pnpm v8 or later β€” install with npm install -g pnpm
  • A Sanity account β€” create one free at sanity.io
  • Access to the GCRA Sanity project β€” request from the project lead
  • Git installed and configured

Getting Started

1. Clone the repository

git clone https://github.com/your-org/Gcralliance-Studio.git
cd Gcralliance-Studio

2. Install dependencies

pnpm install

3. Set up environment variables

Copy the example file and fill in your values:

cp .env.example .env.local

Open .env.local and add the required values β€” see Environment Variables for details.

4. Add CORS origin in Sanity

  1. Go to sanity.io/manage
  2. Select the GCRA project
  3. Navigate to API β†’ CORS Origins
  4. Click Add CORS origin
  5. Add http://localhost:3000 with Allow credentials: βœ“

5. Start the development server

pnpm dev

The Studio is available at: http://localhost:3000/studio

Log in with your Sanity account. On first run, you will be prompted to authenticate via the browser.


Environment Variables

Create a .env.local file in the project root. These variables are required for the Studio and frontend to work:

# ── Sanity (PUBLIC β€” safe in browser) ────────────────────────────────────────
NEXT_PUBLIC_SANITY_PROJECT_ID=your_project_id_here
NEXT_PUBLIC_SANITY_DATASET=production

# ── Sanity (PRIVATE β€” server only, never commit) ──────────────────────────────
# Viewer token β€” for draft previews
# Get from: sanity.io/manage β†’ API β†’ Tokens β†’ Add API token (Viewer)
SANITY_API_READ_TOKEN=sk...

# Editor token β€” for server-side content writes
# Get from: sanity.io/manage β†’ API β†’ Tokens β†’ Add API token (Editor)
SANITY_API_TOKEN=sk...

# Webhook secret β€” used to validate incoming revalidation webhooks
# Generate: openssl rand -base64 32
SANITY_WEBHOOK_SECRET=your_random_secret_here

# ── App ───────────────────────────────────────────────────────────────────────
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_DEPLOY_ENV=development

⚠️ Important: .env.local is listed in .gitignore and must never be committed to Git. Share credentials securely with team members via a password manager.

To get your Project ID:

  • Go to sanity.io/manage β†’ select the GCRA project β†’ the Project ID is shown at the top

To generate API tokens:

  • Go to sanity.io/manage β†’ GCRA project β†’ API β†’ Tokens β†’ Add API token

Available Scripts

Script Command Description
Start dev server pnpm dev Starts Next.js with Studio at /studio
Build pnpm build Production build
Start production pnpm start Serves the production build
Lint pnpm lint ESLint check across all files
Type check pnpm type-check TypeScript compilation check (no output)

Schema Reference

Document Types

These are the main content types editors create and manage in the Studio. Each document type has its own list view in the Studio sidebar.

post β€” Blog Post

Field Type Required Notes
title String βœ“ Post headline
slug Slug βœ“ Auto-generated from title
status Select βœ“ draft or published
publishedAt Datetime Set when publishing
author Reference β†’ author βœ“
categories Reference[] β†’ category
tags String[] Tag array
excerpt Text Max 200 chars β€” used in card views
featuredImage Image With alt text
body Portable Text βœ“ Supports images and callout blocks
readingTime Number Minutes β€” auto-estimated if blank
relatedPosts Reference[] β†’ post Max 3
seo SEO object Meta title, description, OG image

research β€” Research Publication

Field Type Required Notes
title String βœ“ Full publication title
slug Slug βœ“ Auto-generated
researchStatus Select βœ“ ongoing or completed
categories Reference[] β†’ category
authors ResearchAuthor[] βœ“ Name, institution, corresponding flag
abstract Text βœ“ 300–500 words
keyFindings String[] 3–5 plain-language findings
pdfFile File Full paper PDF upload
pageCount Number
journal String Journal / publication name
doi String e.g. 10.1234/example.2024
citation Text APA format citation
relatedResearch Reference[] β†’ research Max 3
seo SEO object

event β€” Event

Field Type Required Notes
title String βœ“
slug Slug βœ“
eventType Select βœ“ conference, workshop, webinar, regional, onboarding
startDate Datetime βœ“
endDate Datetime
timezone String e.g. GMT, WAT, EST
isOnline Boolean Hides location fields when checked
location Object venueName, city, country, address
shortDescription Text Max 150 chars β€” used in cards
description Portable Text βœ“ Full event description
speakers Speaker[] Name, title, photo, bio
registrationUrl URL Calendly or custom form URL
isMembersOnly Boolean
isFree Boolean
travelGrantsAvailable Boolean

ambassador β€” Ambassador

Field Type Required Notes
name String βœ“
photo Image βœ“ Used in map popup and directory card
role String e.g. Country Ambassador
bio Text Max 150 words
contactEmail String Displayed publicly
country String βœ“
region Select βœ“ West Africa, East Africa, South Asia, etc.
coordinates Object lat and lng β€” used for Mapbox map pin
ambassadorSince Date
isActive Boolean Uncheck to hide without deleting
displayOrder Number Lower = appears first in directory

teamMember β€” Team Member

Field Type Required Notes
name String βœ“
title String βœ“ Job title
department Select Leadership, Research, Programmes, Operations, Communications
bio Text Max 200 words β€” shown on About page
photo Image βœ“ Headshot
email String
location String e.g. London, UK
linkedin URL
displayOrder Number Lower = appears first
isVisible Boolean Uncheck to hide from website

category β€” Category

Used to tag blog posts, research publications, and events for filtering.

Field Type Notes
name String e.g. Epidemiology
slug Slug Auto-generated
type Select posts, research, events, all
color Select blue, green, purple, amber, red, teal

author β€” Blog Author

Field Type Notes
name String Full name
slug Slug Auto-generated
photo Image Profile photo
title String e.g. Senior Epidemiologist, LSHTM
bio Text Max 100 words β€” shown below blog posts
linkedin URL
twitter URL

Singleton Documents

These documents exist exactly once in the dataset. They are accessed directly from the Pages and Settings sections in the Studio sidebar β€” not as a list.

Singleton Studio Location Controls
homePage Pages β†’ Home Page Hero, stats, featured research, mission section, membership banner, newsletter section
aboutPage Pages β†’ About Page Hero, mission & vision, core values, history timeline, partners, join CTA
membershipPage Pages β†’ Membership Page Hero, tier cards, training modules, testimonials, FAQ, apply CTA
siteSettings Settings β†’ Site Settings Org name, contact details, office addresses, social links, default SEO
navigationSettings Settings β†’ Navigation Navbar links, dropdowns, footer columns, footer tagline, legal links

Reusable Object Types

These are building blocks used inside documents and singletons. They do not appear as their own list in the Studio.

Object Used in Description
cta homePage, navigationSettings Button label, URL, variant, new tab toggle
seo All document types Meta title, meta description, OG image, noIndex
statItem homePage Value, label, sub-label (e.g. "2,400+ / Active Members")
timelineItem aboutPage Year, title, description for the history timeline
partner aboutPage Name, logo, URL, partnership type
testimonial membershipPage Quote, attribution, member since, photo
faqItem membershipPage Question and answer
membershipTier membershipPage Name, description, features list, eligibility, CTA
trainingModule membershipPage Title, description, duration, availability, platform
socialLinks siteSettings LinkedIn, Twitter/X, Facebook, YouTube, ResearchGate URLs
office siteSettings Office name, address, isPrimary flag
speaker event Name, title, photo, bio
researchAuthor research Name, institution, isCorresponding flag
calloutBlock post, event (rich text) Type (info/warning/success/danger) and text

Studio Navigation Guide

The Studio sidebar is organised into logical sections for editors:

GCRA Content
β”‚
β”œβ”€β”€ πŸ“„ Pages
β”‚   β”œβ”€β”€ 🏠 Home Page        β€” edit hero, stats, mission section, banners
β”‚   β”œβ”€β”€ ℹ️  About Page       β€” edit mission, team section, timeline, partners
β”‚   └── πŸ‘₯ Membership Page  β€” edit tiers, training modules, FAQ
β”‚
β”œβ”€β”€ ✍️  Blog Posts           β€” create and manage blog articles
β”œβ”€β”€ πŸ”¬ Research Publications β€” manage research publications and PDFs
β”œβ”€β”€ πŸ“… Events               β€” create and manage events
β”‚
β”œβ”€β”€ 🌍 Ambassadors          β€” manage ambassador profiles and map coordinates
β”œβ”€β”€ πŸ‘€ Team Members         β€” manage leadership and staff profiles
β”œβ”€β”€ πŸ–ŠοΈ  Authors              β€” manage blog author profiles
β”‚
β”œβ”€β”€ 🏷️  Categories           β€” manage blog, research, and event categories
β”‚
└── βš™οΈ  Settings
    β”œβ”€β”€ 🌐 Site Settings     β€” org details, contact info, social links
    └── 🧭 Navigation        β€” navbar links, footer columns

Content Workflow

Publishing a blog post

  1. Open Blog Posts in the sidebar
  2. Click New Blog Post (top right)
  3. Fill in: Title β†’ click Generate on the slug field β†’ select an Author β†’ set Categories
  4. Write the Excerpt (max 200 chars β€” this appears in card views and as the default meta description)
  5. Upload a Featured Image and fill in the Alt Text field
  6. Write the article in the Body field
  7. Scroll to SEO and fill in a custom meta title and description if needed
  8. Change Status from Draft to Published
  9. Set the Published Date
  10. Click Publish (top right)

The website will automatically update within 60 seconds via the webhook revalidation system.

Adding a research publication

  1. Open Research Publications β†’ New Research Publication
  2. Fill in Title, Abstract, and Key Findings
  3. Add all Authors (mark the corresponding author)
  4. Upload the PDF in the Full Paper field
  5. Set Research Status (Ongoing or Completed)
  6. Set Categories
  7. Add DOI and journal name if available
  8. Set Status to Published and click Publish

Adding a new event

  1. Open Events β†’ New Event
  2. Fill in Title, Event Type, Start Date/Time, and Timezone
  3. Toggle Online Event if applicable β€” the location fields will hide automatically
  4. Fill in Short Description (shown in cards) and Full Description (shown on detail page)
  5. Add Speakers if applicable
  6. Paste the Registration URL (Calendly link or custom form)
  7. Set Status to Published and click Publish

Updating navigation links

  1. Go to Settings β†’ Navigation
  2. Edit the Main Navigation Links array to add, remove, or reorder links
  3. Add dropdown items to any link by expanding it and adding Dropdown Items
  4. Update the Footer Link Columns array for footer navigation
  5. Click Publish

ISR Revalidation & Webhooks

The website uses Incremental Static Regeneration (ISR) with on-demand revalidation. When you publish or update content in the Studio, a Sanity webhook automatically triggers a cache flush on the Next.js frontend so the updated content appears on the live site within seconds β€” without a full redeploy.

How it works

Editor publishes in Studio
        ↓
Sanity sends POST request to webhook URL
        ↓
/api/sanity/revalidate validates the request secret
        ↓
Next.js calls revalidateTag('posts') / revalidatePath('/blog')
        ↓
Affected pages rebuild in the background
        ↓
Updated content served to visitors

Cache tags per document type

Document Type Cache Tags Invalidated
post posts, blog
research research
event events
ambassador ambassadors
teamMember team, about
homePage home
aboutPage about
membershipPage membership
siteSettings site-settings, layout
navigationSettings navigation, layout

Setting up the webhook (production)

  1. Go to sanity.io/manage β†’ GCRA project β†’ API β†’ Webhooks
  2. Click Create webhook
  3. Configure:
    • Name: ISR Revalidation
    • URL: https://yourdomain.com/api/sanity/revalidate
    • Dataset: production
    • Trigger on: Create, Update, Delete
    • Filter: (leave blank β€” triggers on all document types)
    • Projection: (leave blank)
    • HTTP method: POST
    • HTTP Headers: (none needed)
    • Secret: paste the value of SANITY_WEBHOOK_SECRET from your environment variables
  4. Click Save

ImageKit Media Integration

All images uploaded through the Studio are stored in ImageKit and delivered via their global CDN. ImageKit handles resizing, format conversion (WebP/AVIF), and optimisation automatically via URL transforms β€” no image processing happens on the server.

ImageKit folder structure

All Studio uploads are routed to the following folder hierarchy based on the Media Type field:

ghro/
β”œβ”€β”€ production/          ← live website uploads
β”‚   β”œβ”€β”€ site/            ← logos, favicons, OG images
β”‚   β”‚   β”œβ”€β”€ logos/
β”‚   β”‚   └── og/
β”‚   β”œβ”€β”€ team/            ← staff and leadership headshots
β”‚   β”œβ”€β”€ ambassadors/     ← ambassador photos, organised by region
β”‚   β”‚   β”œβ”€β”€ west-africa/
β”‚   β”‚   β”œβ”€β”€ east-africa/
β”‚   β”‚   └── ...
β”‚   β”œβ”€β”€ blog/            ← blog post thumbnails and hero images
β”‚   β”‚   β”œβ”€β”€ 2025/
β”‚   β”‚   └── 2024/
β”‚   β”œβ”€β”€ research/        ← research publication cover images
β”‚   β”œβ”€β”€ events/          ← event banners and speaker photos
β”‚   β”‚   └── banners/
β”‚   β”œβ”€β”€ partners/        ← partner and affiliate logos
β”‚   └── membership/      ← membership tier and testimonial images
β”‚
β”œβ”€β”€ staging/             ← preview/staging environment uploads
└── dev/                 ← local development uploads

⚠️ Note: Research PDFs and application documents are not stored in ImageKit. PDFs upload directly to AWS S3 (HIPAA-eligible storage with a Business Associate Agreement). Only images go through ImageKit.

Naming convention

All folder names and filenames use:

  • Lowercase only
  • Hyphens instead of spaces β€” west-africa/ not West Africa/
  • No special characters
  • Descriptive names β€” helena-whitmore.jpg not IMG_4521.jpg

Deployment

The Studio is deployed as part of the Next.js website on Vercel. There is no separate Studio deployment β€” it is served from the same origin as the frontend at /studio.

Production deployment

Deployments are triggered automatically on every merge to main via the GitHub Actions CI/CD pipeline.

Before deploying to production:

  1. Ensure all required environment variables are set in the Vercel dashboard (not just .env.local)
  2. Confirm the production domain is added to CORS Origins in sanity.io/manage
  3. Confirm the production webhook URL is configured (see ISR Revalidation)

Required Vercel environment variables

Set these in Vercel Dashboard β†’ Project β†’ Settings β†’ Environment Variables:

Variable Environment
NEXT_PUBLIC_SANITY_PROJECT_ID Production, Preview, Development
NEXT_PUBLIC_SANITY_DATASET Production, Preview, Development
SANITY_API_READ_TOKEN Production, Preview
SANITY_API_TOKEN Production
SANITY_WEBHOOK_SECRET Production
NEXT_PUBLIC_APP_URL Production, Preview
NEXT_PUBLIC_DEPLOY_ENV Production (production), Preview (staging)

Contributing

Branch strategy

Branch Purpose
main Production β€” auto-deploys to live site
dev Active development β€” merges go here first
feature/schema-name Schema additions and changes
fix/issue-description Bug fixes

Making schema changes

Schema changes affect the data structure that editors work with. Follow this process:

  1. Create a feature branch from dev:

    git checkout dev
    git pull origin dev
    git checkout -b feature/add-webinar-schema
  2. Make your changes to the relevant schema files in sanity/schemas/

  3. Test locally β€” start pnpm dev and verify the Studio renders correctly with no console errors

  4. Check for breaking changes β€” if you are removing or renaming a field that already has data in production, coordinate with the content team before merging to avoid data loss

  5. Open a Pull Request to dev with a clear description of what changed and why

  6. After merge to main β€” if you added new field types or changed field names, notify the content team so they can update any existing documents

Code style

  • All schema files use TypeScript with Sanity's defineType and defineField helpers
  • Field descriptions should be written in plain English for editors β€” not developers
  • Every image field must include a nested alt text field for WCAG compliance
  • Preview functions should return a meaningful title and subtitle so editors can identify documents in list views

Troubleshooting

Studio shows blank page at /studio

  1. Check that NEXT_PUBLIC_SANITY_PROJECT_ID is set correctly in .env.local
  2. Check that http://localhost:3000 is added to CORS Origins at sanity.io/manage with Allow credentials: βœ“
  3. Open browser DevTools β†’ Console and check for network errors
  4. Try a hard refresh: Ctrl+Shift+R / Cmd+Shift+R

"Unauthorized" error in Studio

  1. Log out of sanity.io in your browser and log back in
  2. Ensure your Sanity account has access to the GCRA project β€” ask the project lead to add you
  3. Re-add localhost:3000 to CORS Origins if it was accidentally removed

Images not loading on the frontend

  1. Confirm cdn.sanity.io is added to next.config.ts under images.remotePatterns
  2. Check that the image field includes the asset-> projection in the GROQ query
  3. For ImageKit images, confirm the NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT env var is set

Webhook not triggering page updates

  1. Check that SANITY_WEBHOOK_SECRET in .env.local matches the secret set in the Sanity dashboard webhook
  2. Check Vercel function logs for errors at /api/sanity/revalidate
  3. Confirm the webhook URL in Sanity uses https:// β€” not http://
  4. Test the webhook manually: Sanity dashboard β†’ API β†’ Webhooks β†’ click the webhook β†’ Test

Content published but website not updating

  1. ISR revalidation can take up to 60 seconds β€” wait and hard refresh
  2. Check the webhook is configured and enabled (green dot in Sanity dashboard)
  3. If revalidation is still not working, trigger a manual Vercel redeploy as a fallback

TypeScript errors in schema files

  1. Run pnpm type-check to see all errors at once
  2. Ensure all defineField calls have a name and type β€” these are required
  3. For reference fields, confirm the target document type slug is spelled correctly in to: [{ type: 'post' }]

License

This repository is private and proprietary. All rights reserved β€” Global Cardiovascular Research Alliance Β© 2025.


Maintained by the GCRA engineering team. For content questions, contact the editorial team. For technical issues, open an issue in this repository.

About

Sanity Studio CMS for managing content of the GCR Alliance platform, including events, research data, and dynamic website content.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors