nelsonarcher.com (this site)

2024, Individual


Creating and evolving nelsonarcher.com has been an ongoing journey that showcases both my technical skills and my approach to web development. What started as a simple portfolio has grown into a full-featured, SEO-optimized platform that not only presents my work but also hosts interactive demos and projects.

Site Architecture & Evolution

This site began as a simple portfolio built with Next.js and React, and it has changed a lot since then. It now runs on Next.js 16 with the App Router, TypeScript, and Tailwind CSS 4 (plus the typography plugin, which styles the write-ups). Blog posts and project write-ups, including this one, are MDX files: markdown with a few React components mixed in. Framer Motion handles the animations, and the calendar page is built on FullCalendar.

Current Structure

Here's how the repository is laid out (key files only):

  • 📁 app (Next.js App Router)
  • 📁 components
  • 📁 content (MDX write-ups)
  • 📁 lib
  • 📁 public
  • 📁 styles
  • 📄 next.config.js
  • 📄 postcss.config.mjs
Click folders to expand (Interactive!)

Key Features & Technical Implementations

Dynamic Content System

Blog posts and project write-ups are files in the repo, and demos are a TypeScript array:

  • MDX Content: Each write-up is a folder under content/ (content/blog/<slug>/index.mdx or content/projects/<slug>/index.mdx) with YAML frontmatter for its metadata and an MDX body.
  • Build-Time Data: lib/content.ts reads the frontmatter with gray-matter, and blogData.ts and projectData.ts turn it into typed BlogPost[] and Project[] arrays (posts sorted newest first, projects by an order field). Adding a post or project means adding a folder; there's no array to edit.
  • Custom Components: Project and blog pages render the MDX body at build time with next-mdx-remote/rsc. A handful of components (Figure, ImageRow, Caption, Carousel, DirectoryTree) can be used in any MDX file without an import. The file tree above is one of them.
  • Demos: A demo has no write-up body, so demos are still a hand-written array in lib/demoData.ts.
  • Dynamic Routing: Next.js dynamic routes ([slug] folders) with generateStaticParams pre-render a page for every project, post, and demo at build time.

Content used to be HTML fragments stored in public/, which meant each fragment was also reachable at its own URL and could get indexed as a duplicate of the real page. content/ sits outside public/, so the source is never served directly, and the old content.html URLs now 301 to the pages they belonged to.

# Frontmatter from content/projects/personal-site/index.mdx (this page)
---
title: "nelsonarcher.com (this site)"
description: "2024 - A personal site to host my projects, blogs, and resume."
subtitle: "2024, Individual"
order: 4
urls:
  - ["Live Site", "https://www.nelsonarcher.com"]
---

Demo entries come in two shapes: internal demos have a url and are embedded in an iframe, while external demos have an externalUrl and get a landing page instead.

// Excerpt from lib/demoData.ts
export const demos: Demo[] = [
  {
    slug: "hurricane",
    title: "Hurricane Data Visualizer",
    description: "Track hurricanes and weather patterns, and quickly visualize data from NOAA.",
    thumbnailBase: "/demos/hurricane/thumbnail",
    thumbnailExt: "gif",
    ogImage: "/demos/hurricane/og",
    fullPage: true,
    hidden: false,
    url: "https://hurricane-adam.vercel.app",
    category: 'personal',
    techStack: ['React', 'TypeScript', 'Next.js', 'Mapbox'],
    keywords: [
      "hurricane tracker",
      "NOAA data visualization",
      "interactive weather map",
      // More keywords for SEO...
    ]
  },
  {
    slug: 'pingpong',
    title: 'Ping Pong Pi',
    description: 'An Elo-based scoring system and live scoreboard for tracking ping pong stats.',
    thumbnailBase: '/demos/pingpong/thumbnail',
    externalUrl: 'https://pingpongpi.com',
    hidden: false,
    category: 'personal',
    techStack: ['React', 'TypeScript', 'Next.js', 'Raspberry Pi', 'Python', 'Firebase']
  },
  // More demos...
];

Interactive Demos Section

One of the most significant additions to the site is the Demos section, which showcases interactive projects and applications. Each demo gets a page at /demos/<slug> that either embeds the app or links out to it:

  • iframes for Internal Projects: Demos with a url are embedded full-page in an iframe, with JSON-LD and a noscript link as a fallback for crawlers.
  • Landing Pages for External Projects: Demos hosted on their own domains get a landing page here with the description, tech stack, structured data, and a "Launch" link. There's no automatic redirect; the visitor decides when to open the app.
  • Custom Demo Layouts: Every demo page sets its own title and description. Embedded demos also get a canonical URL, keywords, and their own favicon and Open Graph image from public/ when one exists, falling back to the site defaults.

Calendar Page

/calendar shows my schedule in a FullCalendar view. The page fetches events from an API route (app/api/calendar/route.ts) that reads my public Google Calendar and, when it's configured, merges in the published ICS feed of my Outlook work calendar. Work events show up in grey next to my personal events in blue. The server passes along only their titles and times (private ones just say "Busy"), and if the work feed can't be reached the calendar falls back to the Google events alone.

SEO Optimization

The site incorporates comprehensive SEO optimizations:

  • Dynamic Metadata: Each page has custom metadata generated through the Next.js Metadata API, including titles, descriptions, and Open Graph data.
  • Sitemap: app/sitemap.ts builds /sitemap.xml from the same project, blog, and demo data the pages use, leaves out hidden items, and gives the main pages and demos a higher priority.
  • Structured Data: Schema.org JSON-LD markup provides search engines with detailed information about content and projects.
  • Canonical URLs: Pages declare their canonical URL, and short or outdated paths (like /hurricane or the old content.html fragments) 301 to the canonical page.
  • Robots Control: A static public/robots.txt allows crawling, keeps crawlers out of /api/, and points to the sitemap. Page metadata adds robots directives on top of that.
// Excerpt from app/layout.tsx - Structured data for projects
<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{
    __html: JSON.stringify({
      "@context": "https://schema.org",
      "@type": "ItemList",
      "itemListElement": [
        {
          "@type": "SoftwareApplication",
          "position": 1,
          "name": "Hurricane Data Visualizer",
          "description": "Track hurricanes and weather patterns, quickly visualize data from NOAA",
          "url": "https://www.nelsonarcher.com/demos/hurricane",
          "applicationCategory": "WebApplication",
          "operatingSystem": "Any"
        },
        // More projects...
      ]
    })
  }}
/>

Advanced UI Components

The site features several custom UI components that enhance the user experience:

  • Starry Background: The interactive star field on the home page creates an engaging first impression.
  • Typing Animation: Custom typing animations add a dynamic element to headings and titles.
  • Velocity Scroll: A horizontal scrolling component that adjusts speed based on user scrolling provides a unique way to display additional information.
  • Fallback Images: Smart image loading with fallbacks ensures visuals always display properly, even with different formats.
  • Framer Motion Animations: Smooth transitions and animations throughout the site improve the overall user experience.

Performance Optimizations

The site includes several performance optimizations:

  • Image Preloading: About a second after the page loads, the root layout preloads every project, blog, and demo thumbnail so the listing pages don't pop in. The URL list is built on the server (lib/preloadUrls.ts) and passed to a small client component, because the data modules read the filesystem and can't be bundled for the browser.
  • Response Headers: Custom cache headers, security headers, and content-type options improve both performance and security.
  • Static Generation: Using Next.js static generation for content pages improves load times.
  • Lazy Loading: Components and images use lazy loading to reduce initial page weight.
  • Font Optimization: Local font loading with variable fonts reduces web font impact.
// Excerpt from next.config.js - Performance and security headers
async headers() {
  return [
    {
      source: '/(.*)',
      headers: [
        {
          key: 'X-Content-Type-Options',
          value: 'nosniff',
        },
        {
          key: 'X-Frame-Options',
          value: 'SAMEORIGIN',
        },
        {
          key: 'X-XSS-Protection',
          value: '1; mode=block',
        },
        {
          key: 'Referrer-Policy',
          value: 'strict-origin-when-cross-origin',
        }
      ],
    },
    // Cache control for static assets
    {
      source: '/(.*).(jpg|jpeg|png|gif|webp|svg|ico|ttf|woff|woff2)',
      headers: [
        {
          key: 'Cache-Control',
          value: 'public, max-age=31536000, immutable',
        }
      ],
    },
  ];
}

Project Showcase System

The site features two distinct types of project showcases, both listed on the projects page:

  • Project Documentation: Traditional project write-ups with detailed explanations, code samples, and screenshots (like this page).
  • Interactive Demos: Live, functional applications that visitors can use directly, showcasing practical implementations and real-world use cases.

This dual approach provides both in-depth technical information for those interested in the development process, and practical demonstrations for those who want to see the technology in action.

Featured Interactive Projects

Some of the key interactive projects currently showcased include:

  • Hurricane Tracker: An interactive visualization tool for tracking hurricanes and weather patterns using NOAA data.
  • UH Comp Sci Discord List: A directory of University of Houston Computer Science Discord servers.
  • Attendance Tracker: A weighted attendance management system with grouping features.
  • Ping Pong Pi: An Elo-based scoring system for tracking ping pong statistics.

Technical Challenges & Solutions

SEO for External Applications

One of the significant challenges was making external applications discoverable through search engines. The solution involved:

  • Creating a landing page for each external application that describes it and links to it, rather than sending visitors straight to the external URL
  • Implementing structured data that helps search engines understand what each application does
  • Adding preconnect hints for the main demo domains to speed up loading
  • Adding alternate URL links to establish relationships between the main site and external applications

Content Management

Managing multiple types of content (projects, demos, blog posts) required a structured approach:

  • TypeScript interfaces define the shape of each content type (BlogPost, Project, Demo)
  • Write-up metadata lives in frontmatter next to the content, and the data modules build their arrays from it at build time
  • Because those modules read the filesystem, they stay server-only; client components receive the data as props
  • Dynamic routes handle slug-based content loading

Hosting & Deployment

The site is hosted on Vercel, which provides several advantages for a Next.js application:

  • Seamless Git Integration: Automatic deployments when changes are pushed to the main branch
  • Preview Deployments: Every pull request gets a unique preview URL
  • Edge Network: Content is served from edge locations for faster global access
  • Custom Domain Support: Easy configuration for multiple domains (nelsonarcher.com, adamnelsonarcher.com)

Future Development Plans

A few things I'd still like to add:

  • Blog Organization: Categories, tags, and related posts for the blog
  • More Interactive Demos: Additional live applications as I build them
  • Improved Accessibility: Further work to make the site more accessible to all users

Conclusion

nelsonarcher.com is where I write up projects, post the occasional blog, and host live versions of things I've built. Under the hood it's a statically generated Next.js app with its content stored as MDX in the repo, so adding a project or post is a new folder and a push. It keeps changing as I add projects and clean up older parts of the code.