Skip to content

gah-code/gilbertoaharo

Repository files navigation

Gilberto A. Haro — Personal Site

Single-page React + TypeScript + Vite site powered by Contentful.
The landing page is composed from modular CMS sections, and article pages render deep dives with Rich Text.


Table of Contents


Overview

This repo powers a personal landing experience and long-form writing pages:

  • Landing (/) renders a single pagePersonalLanding entry composed of modular section entries.
  • Articles (/articles/:slug) render article entries (Rich Text + optional hero image + attachments).
  • A lean router keeps this as a lightweight SPA without adding a routing dependency.

Features

  • Content-driven landing page from Contentful modular sections
  • Article pages with SEO metadata fallbacks
  • Minimal, reusable UI primitives (src/components/ui)
  • Small, explicit “content layer” (src/content/) that isolates CMS concerns from UI concerns
  • Custom SPA router with internal link interception

Stack

  • React 19, TypeScript, Vite (path alias @src)
  • Contentful delivery SDK + light mappers (src/content/contentful)
  • Minimal design primitives and section renderers (src/components/ui, src/components/sections)
  • Custom SPA router for:
    • /
    • /articles/:slug
    • 404

Project Structure

High-level map:

  • src/pagesLandingPage, ArticlePage, NotFoundPage
  • src/router — path parsing and SPA navigation helpers
  • src/content — source contract + Contentful client/API/adapters/types; static/ holds fixtures for UI-first prototyping
  • src/components/sections — per-section renderers driven by CMS content type id
  • src/components/ui & src/components/layout — lightweight UI primitives and SEO wrapper
  • src/components/rich-text — minimal rich text renderer
  • src/styles — tokens + base resets
  • docs/ — IA, design system notes, CMS guidance

📁 Project Structure — gilbertoaharo

Architecture stance: Single-Page App (Vite + React) with a clean CMS boundary, a lightweight router, and a design-system-driven UI. Content models live outside UI logic and flow through adapters before rendering.

gilbertoaharo/
├─ .env.example               # Sample env vars (Contentful, site config, preview flags)
├─ LICENSE                    # Project license
├─ README.md                  # Project overview, setup, and architecture notes
├─ eslint.config.js            # ESLint rules (React + TS)
├─ index.html                 # Vite HTML entry template
├─ package.json               # Scripts, dependencies, metadata
├─ package-lock.json          # npm dependency lockfile
├─ tsconfig.json              # Base TS config with project references
├─ tsconfig.app.json          # TS config for the app bundle
├─ tsconfig.node.json         # TS config for Node/Vite tooling
├─ tsconfig.tsbuildinfo       # TypeScript incremental build cache (generated)
├─ vite.config.ts             # Vite dev/build config (aliases, plugins, tests)
│
├─ public/                    # Static assets copied as-is to build output
│  ├─ _redirects              # SPA redirect rules (Netlify-style hosting)
│  └─ vite.svg                # Example static asset
│
└─ src/                       # Application source
   ├─ main.tsx                # React entry point (mounts <App />)
   ├─ App.tsx                 # Root app component (router + global wiring)
   ├─ App.css                 # Legacy Vite starter styles (currently unused)
   ├─ index.css               # Legacy Vite global styles (currently unused)
   ├─ env.ts                  # Centralized env parsing + defaults (fail-fast)
   ├─ vite-env.d.ts           # Vite/TS env type declarations
   │
   ├─ assets/                 # Bundled app assets (imported by JS/TS)
   │  └─ react.svg
   │
   ├─ styles/                 # Global styling layer
   │  ├─ tokens.css           # Design tokens (colors, spacing, typography)
   │  └─ base.css             # Base resets/global styles (imports tokens)
   │
   ├─ router/                 # Lightweight SPA routing (no react-router)
   │  ├─ routes.ts            # Route parsing & route definitions
   │  ├─ Router.tsx           # Route state + view selection
   │  └─ link.ts              # Internal navigation helpers (history-based)
   │
   ├─ pages/                  # Route-level views (thin, data-driven)
   │  ├─ LandingPage.tsx      # `/` — Personal landing page
   │  ├─ ArticlePage.tsx      # `/articles/:slug` — Long-form article view
   │  ├─ NotFoundPage.tsx     # 404 fallback
   │  └─ DebugPage.tsx        # Debug/diagnostics view (CMS visibility)
   │
   ├─ components/             # UI components (design system + composition)
   │  ├─ layout/              # Page-level layout & chrome
   │  │  ├─ PageShell.tsx     # Page wrapper (SEO, spacing, structure)
   │  │  └─ SeoHead.tsx       # Document head + meta tags
   │  │
   │  ├─ rich-text/           # Controlled rich-text rendering
   │  │  └─ RichTextRenderer.tsx
   │  │     # Maps allowed Contentful nodes → UI primitives
   │  │
   │  ├─ sections/            # Content-driven page sections
   │  │  ├─ SectionRenderer.tsx # Switch on section content-type ID
   │  │  ├─ SectionShell.tsx   # Shared section framing (anchors, spacing)
   │  │  ├─ HeroSection.tsx
   │  │  ├─ ProjectsSection.tsx
   │  │  ├─ SkillsSection.tsx
   │  │  ├─ TimelineSection.tsx
   │  │  ├─ LearningSection.tsx
   │  │  └─ ContactSection.tsx
   │  │
   │  └─ ui/                  # Design-system primitives (reusable atoms)
   │     ├─ Badge.tsx         # Badge / label primitive
   │     ├─ Button.tsx        # Button primitive (CTA)
   │     ├─ Card.tsx          # Card surface primitive
   │     ├─ Container.tsx     # Layout container primitive
   │     ├─ Heading.tsx       # Heading typography primitive
   │     ├─ Link.tsx          # Styled anchor primitive
   │     ├─ Stack.tsx         # Stack/spacing layout primitive
   │     └─ Text.tsx          # Text typography primitive
   │
   ├─ content/                # Content layer (CMS abstraction boundary)
   │  ├─ source.ts            # ContentSource interface (UI-first contract)
   │  │
   │  ├─ static/              # UI-first prototyping (no CMS dependency)
   │  │  ├─ fixtures.ts       # Local fixture content data
   │  │  └─ staticSource.ts   # Static ContentSource implementation
   │  │
   │  └─ contentful/          # Contentful implementation of ContentSource
   │     ├─ client.ts         # Contentful SDK client (delivery/preview)
   │     ├─ api.ts            # Raw Contentful query helpers
   │     ├─ includes.ts       # Include/reference depth helpers
   │     ├─ types.ts          # Contentful model/type definitions
   │     ├─ adapters.ts       # CMS → UI data mapping (contracts live here)
   │     └─ contentfulSource.ts # Contentful ContentSource implementation
   │
   └─ preview/                # Preview-mode support (draft content)
      ├─ previewMode.ts       # Preview state helpers (env + toggles)
      └─ PreviewBanner.tsx    # Preview mode UI indicator

Getting Started

Prerequisites

  • Node: 20.19+ or 22.12+
  • npm

Install

npm install

Run locally

npm run dev

Open: http://localhost:5173


Environment Variables

Copy .env.example to .env.local (recommended) and fill in your values:

cp .env.example .env.local

Required:

  • VITE_CONTENTFUL_SPACE_ID
  • VITE_CONTENTFUL_DELIVERY_TOKEN
  • VITE_CONTENTFUL_ENVIRONMENT (defaults to master)

Recommended:

  • VITE_ARTICLE_ROUTE_PREFIX (defaults to /articles)
  • VITE_SITE_URL (absolute URL for canonical fallbacks)

Optional (if you support draft preview later):

  • VITE_CONTENTFUL_USE_PREVIEW
  • VITE_CONTENTFUL_PREVIEW_TOKEN

Never commit .env.local or tokens. Commit .env.example only.


How It Works

Landing page

  • LandingPage fetches the single pagePersonalLanding entry.
  • The page’s sections[] are rendered by SectionRenderer.
  • SectionRenderer dispatches to a section component based on Contentful content type id.

Article pages

  • ArticlePage fetches an article entry by slug.

  • Renders:

    • title / excerpt
    • optional hero image
    • RichTextRenderer for body
    • optional attachments list
  • SEO meta is applied via SeoHead:

    • <title>
    • meta description
    • canonical link fallback

Router

  • Router listens to popstate and routes between landing, article, and not-found states.
  • A lightweight Link / navigation helper intercepts internal links for SPA navigation.

Content source contract

  • src/content/source.ts defines the ContentSource contract.
  • contentfulSource is the default implementation.
  • A staticSource exists for UI-first prototyping via fixtures (optional workflow).

Local Preview Checklist

Use this when “it loads but content is missing”:

  1. Contentful tokens are present and correct (.env.local)

  2. You’re using the right environment (VITE_CONTENTFUL_ENVIRONMENT)

  3. Entries are Published (Delivery API only)

  4. pagePersonalLanding exists and has sections[]

  5. At least one article exists with a known slug (e.g. article-test)

  6. Visit:

    • / (landing)
    • /articles/<slug> (article route)

Scripts

npm run dev        # start local dev server
npm run build      # type-check + build
npm run preview    # preview built app
npm run lint       # eslint

Troubleshooting

401 Unauthorized

  • Missing/invalid Contentful token
  • Fix: verify VITE_CONTENTFUL_DELIVERY_TOKEN, restart dev server

Landing page loads but no sections

  • pagePersonalLanding missing, unpublished, or sections[] empty
  • Fix: publish the entry and ensure sections[] contains the section entries

Article 404

  • Wrong route prefix or slug mismatch
  • Fix: confirm route prefix (/articles) and the article’s slug field

Missing nested references

  • Include depth too low or referenced entries unpublished
  • Fix: increase include depth in fetch logic and publish referenced entries

Content Modeling Notes

  • Internal navigation should prefer references over hard-coded URLs:

    • projectLink.article (internal) wins over projectLink.url (external)
  • Rich Text is intentionally guarded; the renderer supports a minimal set of nodes.

  • Slugs are treated as stable identifiers once published.


Safety & Publishing

  • Secrets: .env, .env.* are gitignored; commit .env.example only.

  • Audit: search for credentials before pushing:

    rg -i "secret|token|password|apikey"
  • Dependencies: lockfile is package-lock.json.

  • Docs are tracked and safe to publish (no env values in docs).


Docs

  • docs/ia.md — Information architecture & content mapping
  • docs/editorial-guidelines.md — writing/SEO/link rules
  • docs/design-system.md — UI primitives and component patterns

About

2026 | Personal SPA React + Design-system-driven UI + Vite site powered by Contentful.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published