Creating adamnelsonarcher.com (or just nelsonarcher.com) took about 2 weeks, and went through many cycles of building and restarting.
Initially, I created a site in google sites, basically just a place to throw my projects and resume, along with some information about what I was currently working on. While this was effective, a computer science person like myself really needs something more impressive to use as an online portfolio. Also, during my last job, I was told that my biggest weakness was my lack of skills with next.js and react. So, wanting an upgrade on both my site presentation and my web development skills, I chose to build this site from the ground up.
I created the basic template with the built in feature in next.js, populating my directory with some of the necessary infrastructure to get a site template running. From there, I only had basic ideas for a home page, and I built that first. I knew I wanted something eye-catching, and given that my recent projects have focused on the moon, I went for an interactive star display.
Home Page
Creating the shooting star display was not too difficult, I just created a React component with useEffect to manage everything. First, I grabbed the canvas reference and set up the 2D context to draw on. I created a resizeCanvas function that adjusts the canvas width and height whenever the window resizes. Once the canvas is the right size, I initialized the stars with random positions, sizes, and speeds in the initStars function, based on the total area of the canvas. This way, the stars fill up the space, regardless of window size. For the shooting stars, I have a function that generates random shooting stars with varying speeds and lengths. These stars fly across the screen and gradually fade out. They are generated at random times, but I cheated a bit and guaranteed that one appears within 5 seconds of site load, to make sure people can see this feature.
To make the stars more interactive, I added an event listener for mouse movement. This allows the stars to shift in response to how fast and in which direction the mouse is moving. But for mobile users, there is no mouse to track, so I just made the stars move autonomously using sine and cosine functions.
Starry Background Code Snippet
Here is the code that controls the starry background:
// Code snippet from components/ui/starry-background.tsx
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
initStars();
};
const initStars = () => {
starsRef.current = [];
const numStars = Math.floor((canvas.width * canvas.height) / 10000);
for (let i = 0; i < numStars; i++) {
starsRef.current.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 1.5,
speed: Math.random() * 0.5 + 0.1,
});
}
};
// ... (rest of the code)
});
Backend Structure and New Posts
Building the component structure was key to making the site consistent and easy to manage. I created a few reusable components for the layout, header, and post content, which helped to streamline the process. For example, the "layout" component wraps every page, which guarantees the style stays consistent across the site. Regardless of what page we are on, the header and footer are in the right spots. The "post content" component, for example, regulates the style of all blog posts and project posts. Whether you are on a recent blog or a project from last year, the titles will look the same, and they will use the same page styles. This also allows me to edit the style of all of my pages easily, as my projects and blogs are actually stored as raw HTML (I know, this is not ideal). This was the cleanest way I could think of for easy post creation, as I just drop an HTML file into a folder, and the rest is handled.
For the blogs and projects, I made it dynamic so that content can be loaded based on the URL using file-based routing in Next.js. When a user visits a post, the URL slug (the part of the URL that uniquely identifies the post) is used to determine which post to load. I wrote a function that searches through my posts array to find the correct post based on the slug. Once it finds the post, the function grabs the actual content from a markdown or HTML file located in the public
directory and reads it asynchronously. This keeps the content and layout separate, which makes updating or adding new blog posts very simple.
Directory Structure
This page, for example, has the slug personal-site
, which is a folder in my public/
folder. Here's an interactive view of the directory structure:
- public
- app
- lib
- components
Within personal-site
, there is a thumbnail image and an HTML file. The main page for projects defines a project card, which uses the title, thumbnail, and description from both the personal-site
folder and the projects.tx folder in public/
and lib/
. An example of the lib/projectData.ts
outline is here:
export const projects: Project[] = [
{
slug: "reinforcement-learning-multi-agent",
title: "Interactive Analysis of Reinforcement Learning in Multi-Agent Systems",
description: "2024 - A built-out GUI for tweaking and playing with SARSA and Q-learning strategies",
thumbnailBase: "/projects/reinforcement-learning-multi-agent/thumbnail",
contentFile: "/projects/reinforcement-learning-multi-agent/content.html",
subtitle: "2024, Group",
urls: [
["Project Report", "https://docs.google.com/document/d/1n4p5mlrmTRLL90nukS47a-bfTqnGFLhJU00xOXPZfPY/pub"],
["GitHub", "https://github.com/spartan24032/Project-AI"]
],
},
// ... other projects
];
This allows me to avoid repeating the card creation process, as all of that layout stuff is automated, and only determined by how many posts are present in my public folder.
Styling and CSS
For styling nelsonarcher.com, I used Tailwind CSS, a framework that streamlines the process of creating responsive designs. Tailwind's predefined classes allowed me to apply styles directly in the HTML, reducing the need for extensive custom CSS and ensuring consistency across the site.
Benefits of Tailwind:
- Tailwind's responsive modifiers made it easy to ensure the site looks great on different screen sizes, from mobile to desktop.
- I extended Tailwind's default theme to include a custom color palette and typography that align with my vision, defined in the
tailwind.config.js
file. - Using Tailwind's utility classes helped maintain a consistent style across all pages and components.
Hosting
Hosting my site on Vercel turned out to be a really smooth experience. I chose Vercel because of its known integration with Next.js, I really wanted something that would be easy to host and update. Once I connected my GitHub repo to Vercel, the platform detected my Next.js project and configured the build process. The deployment is triggered every time I pushed changes to the main branch, meaning I didn't have to worry about manual deployments, which is pretty sweet.
Setting up custom domains was also pretty straightforward. I had already purchased several domains, (nelsonarcher.com, adamnelsonarcher.com), one through CloudFlare and one through Ionos. I would recommend CloudFlare over Ionos, as Ionos is slow and was honestly a pretty bad experience overall. I liked how affordable it was, but I chose to spend $9 extra per year just to buy and use my domain through CloudFlare.
Overall, hosting with Vercel has been a smooth experience. I use Google search console to monitor traffic, and CloudFlare to manage DNS settings. I was using CloudFlare to control domain stuff when I was using google Sites, but Vercel handles all of that as well, making things surprisingly simple on this front.
Performance Optimization
To enhance site performance, I implemented several optimizations. I used lazy loading for images to reduce initial load times and optimized animations with libraries like framer-motion
to ensure smooth interactions. These strategies helped improve the site's speed and user experience, especially on mobile devices. Another area I focused on was minimizing unnecessary JavaScript. JavaScript can be a performance bottleneck, so I made sure to only load the scripts that were necessary for a given page. The issue I would run into was with how I was loading my blog pages. I load them as raw HTML, and trying to get JavaScript working with that was a total pain. I had to create a js file in my public
folder, and patch the busyness with a lot of CSS. It was a total headache, and made me wish that I had implemented my blog pages differently.
Planned Features
Looking ahead, I plan to add an interactive section where my projects and apps can be hosted. In theory, people could run my projects and get to see why they are helpful. Some projects, like my ping pong pi, could even be hosted and used by the general public, not just people I want to hire me. There is a chance that this exists at the time you are reading this. If my site has a "demos" page in the header, it does. If not, I am probably going to implement that soon.