How this website was made

A current look at the technology, content model, and Vercel deployment behind this website.

Abstract technical illustration of a Git repository flowing through deployment infrastructure into a custom domain.

This website is a deliberately small, static-first application. It gives me a place for my professional profile, CV, and writing without introducing a content management system, database, or long-lived application infrastructure. The current stack is Next.js 15, React 19, and TypeScript, deployed on Vercel from a GitHub repository.

The architecture is intentionally conventional. Most of the site is rendered on the server and prerendered during the production build. Content lives beside the code, deployment follows a push to GitHub, and Vercel serves the result through a custom domain.

Application stack

The site uses the Next.js App Router. Each top-level section has its own route under app, while individual articles are handled by the dynamic app/blog/[slug] route. This provides file-based routing, route-specific metadata, image optimisation, and static generation without a separate backend.

React Server Components remain the default. The only client-side component is the reactive background, which listens for pointer movement and updates a small set of CSS custom properties. Keeping that interaction isolated means the main content does not need to hydrate in the browser.

The practical stack is:

  • Next.js 15 using the App Router;
  • React 19 and React Server Components;
  • TypeScript with strict type checking;
  • global CSS for the editorial design system and responsive layouts;
  • next/image for optimised article and profile imagery;
  • ESLint and the Next.js Core Web Vitals rules for static analysis.

Content and static generation

Blog posts live in content/blog as Markdown or MDX files with frontmatter. Each post supplies a title, description, and ISO-formatted date, with optional banner image metadata.

The content loader in lib/posts.ts reads those files at build time, uses gray-matter to separate frontmatter from the article body, validates the required metadata, and returns typed post objects. The blog index sorts those objects by date, while generateStaticParams() enumerates every slug so Next.js can prerender each article.

The article body is rendered by a small local Markdown renderer in lib/markdown.tsx. It supports the elements this blog currently needs: headings, paragraphs, links, inline code, and lists. That narrow feature set is intentional. It keeps the content path easy to understand and avoids adding a general-purpose MDX runtime until richer embedded components are genuinely useful.

There is no CMS or database. Publishing still means adding or editing a content file and committing the change, but that is an implementation detail rather than the subject of the blog itself. The writing is focused on cybersecurity and leadership lessons drawn from real situations.

Visual implementation

The interface is built as an editorial system rather than a component-library theme. Shared typography, colour variables, cards, panels, timelines, article styles, and responsive behaviour live in app/globals.css. Route components add semantic structure while reusing those shared classes.

Article banners use next/image, with their paths and alternative text supplied through frontmatter. The site also respects reduced-motion preferences, allowing the decorative background to recede without affecting navigation or content.

Build and deployment on Vercel

The production application is connected to the GitHub repository and deployed by Vercel. The default production path is intentionally short:

  • a change is committed and pushed to the repository;
  • Vercel installs the locked dependency tree from package-lock.json;
  • the platform runs the standard Next.js production build;
  • Next.js prerenders the known routes and blog slugs;
  • Vercel publishes the build to its global delivery network.

The repository does not need a custom vercel.json or custom Next.js deployment configuration. Vercel detects the framework from package.json, uses the normal npm run build command, and understands the resulting static and server artifacts automatically. Local Vercel project metadata lives in .vercel, which is excluded from version control because it is specific to the linked local environment.

The deployed blog pages are currently returned as prerendered Next.js responses and cached by Vercel. That matches the intended operating model: content changes trigger a new immutable deployment, while readers receive static output without waiting for a database or application server to assemble the page.

Domain and routing

Namecheap remains the domain provider, while Vercel terminates HTTPS and serves the application. www.rogerribas.me is the canonical production address, and requests to the apex domain, rogerribas.me, redirect to www.

This separation keeps responsibilities simple:

  • GitHub stores the source and content history;
  • Next.js builds the application and prerenders the routes;
  • Vercel creates deployments, caches the output, and manages HTTPS;
  • Namecheap manages the domain registration and DNS configuration.

Why this shape still works

The site has grown beyond its first version, but the underlying model has not needed to become more complicated. The About and CV pages can evolve as regular React routes, articles remain reviewable text files, and every production change passes through the same reproducible build.

If the blog eventually needs interactive diagrams, authenticated features, or frequently changing data, Next.js and Vercel leave room for server functions, incremental regeneration, and richer client components. Until there is a real need for those capabilities, a typed, static-first application remains the most maintainable choice.