This repository is a Next.js (App Router) site with content-driven pages and lightweight, presentational components. The goal is to keep copy/data in content/*, define types in lib/types.ts, and compose pages using local page components.
Principles
- Content-driven: copy/data live in
content/*. Components are presentational and prop-only. - Type-first: define data shapes in
lib/types.tsbefore using in content/components. - Server-first: prefer Server Components; use
"use client"only for interactive pieces (e.g., FAQ).
Quick Start
- Install:
npm install - Dev:
npm run dev→ http://localhost:3000 - Build:
npm run buildthennpm run start - Lint:
npm run lint
Key Directories
app/(marketing)/*/page.tsx— page entries (Server Components)components/sections/*— reusable sections (Hero, WhoWeAre, CTA, Companies)components/associates-components/*— Associates page local components (Header, Info, Roles, FAQ, Voices, ApplicationProcess, Footer)- Note: directory name has a typo (
componenets). Keep as-is or fix if desired.
- Note: directory name has a typo (
components/team-components/*— Team page local components (single-file entry:Team.tsx)components/companies-components/*— Companies page local components (single-file entry:Companies.tsx)content/*— content sources (fellowship.ts,team.json,navigation.ts,companies.ts)lib/*— types/utils/SEO (types.ts,utils.ts,seo.ts)public/*— static assets- Styles: Tailwind (
styles/globals.css,tailwind.config.ts)
Pages
- Home
/—app/(marketing)/page.tsxassemblescomponents/sections/*. - Associates
/associates- Data:
content/associates.ts(typed viaFellowshipContentin types) - Render:
app/(marketing)/associates/page.tsxpasses props to local components undercomponents/associates-components/*.
- Data:
- Team
/team- Data:
content/team.json(TeamMembertype) - Local page component:
components/team-components/Team.tsx - Usage (CMS-ready):
import React from "react"; import { getTeamMembers } from "../../../lib/cms"; import Team from "../../../components/team-components/Team"; import TeamHero from "../../../components/team-components/TeamHero"; export const revalidate = 300; export default async function TeamPage() { const members = await getTeamMembers(); return ( <> <TeamHero /> <Team members={members} groupBy={true} title="Our Team" showHeader={false} /> </> ); }
- Data:
- Companies
/companies- Data:
content/companies.ts(Companytype) - Local page component:
components/companies-components/Companies.tsx - Disabled by default; to enable rendering:
import Companies from "../../../components/companies-components/Companies"; import { companies } from "../../../content/companies"; export default function CompaniesPage() { return <Companies companies={companies} title="Partner Companies" />; }
- Current file returns
nullwith instructions until content/design is finalized.
- Data:
Components & Styling
- Do not hard-code copy in components; pass data via props from pages/content.
- Use Tailwind for layout/colors/responsive (
sm,md,lg). - Images live in
public/; prefer Next/Image where appropriate.
Types & Content
- Add/adjust types in
lib/types.ts(e.g.,TeamMember,Company,FellowshipContent). - Ensure
content/*adheres to the defined types. - When adding fields: update
types.ts, then the relevantcontent/*files and component props.
Common Tasks
- Update navigation: edit
content/navigation.ts. - Edit Associates content: update
content/associates.ts. (type stillFellowshipContentin lib/types) - Edit Team members: update
content/team.json. - Enable Companies page: populate
content/companies.tsand switch the page to renderCompanies.
Workflow
- Branch per feature/page (e.g.,
feat/team-page). - Semantic commits (
feat,fix,docs,refactor,chore). - Before merging: test key breakpoints and include screenshots or notes.
Notes
- Fellowship uses a dedicated local component directory due to higher page complexity.
- Team and Companies now follow the same pattern with local page components to keep structure consistent.
CMS Integration (Safe on Vercel)
- Automatic readiness: CMS reads remote content when required envs are present; otherwise falls back to local
content/*. - Team page now uses
lib/cms.getTeamMembers()with robust fallback tocontent/team.jsonif envs are missing or remote fetch fails. - ISR:
export const revalidate = 300on/teamkeeps the page statically optimized and refreshes every ~5 minutes. Admin publishing triggers on-demand revalidation. - Admin UI: visit
/admin→ password screen (cookie-based auth). The login API/api/admin/loginvalidatesADMIN_PASSWORDand sets anadmin_authcookie. - API: POST
/api/admin/update-teamcommits JSON to GitHub via the Contents API, requires theadmin_authcookie, and callsrevalidatePath("/team").
Environment variables
ADMIN_PASSWORD— required to access/adminand publish.GITHUB_REPO—owner/repo.GITHUB_BRANCH— branch name (defaultmain).TEAM_FILE_PATH— path to JSON in repo (defaultcontent/team.json).GITHUB_TOKEN— token withreposcope (needed for private repos and API commits).
Rollout recommendations on Vercel
- Preview: set all env vars, verify
/adminand publishing. Remote reading becomes active automatically when envs are present. - Production: you can ship without GitHub envs; the site reads local content. Once envs are added, remote reading becomes active automatically. No toggles needed.
- No server-side file writes are used; all content changes go through GitHub commits or remote reads, which is compatible with Vercel’s immutable deployments.
Local development
- Add envs in
.env.local:ADMIN_PASSWORD=changeme GITHUB_REPO=owner/repo GITHUB_BRANCH=main TEAM_FILE_PATH=content/team.json GITHUB_TOKEN=ghp_... - Run
npm run dev, openhttp://localhost:3000/admin, enter the admin password (envADMIN_PASSWORD, e.g.123456), then paste team JSON and Publish.