Project Update2026-06-01

Rebuilding mcallbos.co Without Starting Over

How I rebuilt mcallbos.co from a static portfolio into a Next.js portfolio and blog, while keeping the original design and making the codebase easier to extend.

I built mcallbos.co around the time I was graduating from SUNY Fredonia. Back then, I needed a portfolio site that made a strong first impression and gave people a clear sense of what I could build, and it did that job pretty well. I liked it enough that I barely touched it for about a year and a half, aside from small content updates. That was a good sign from the outside. Underneath, it also meant the code was still carrying decisions I made before I had worked on larger, longer-lived systems. My work at Jolera has pushed me to think more about maintainability, deployment, ownership, and systems that can keep growing after the first version ships. VLViewer, and its popularity taught me the same lesson in a different way. It exposed some bottlenecks in how I used to structure projects, especially once content, feeds, and deployment automation started to matter. So when I decided to add a blog to mcallbos.co, I did not want to redesign the portfolio just to make it feel new. The UX still felt right. The animated background, glass panels, project sections, and overall visual identity still felt like the site.

Simple to Deploy, Brittle to Extend

The first version of the portfolio was intentionally lightweight. It was static, dependency-free, and easy to host. There was no framework and no build step. The frontend loaded JSON files and used browser-side JavaScript to render the parts of the page that I wanted to update over time. That approach worked because the site was small and mostly single page. It kept deployment simple and made the site portable. But the simplicity was mostly external. Inside the codebase, a lot of behavior depended on scripts finding the right DOM nodes at the right time. The background, panels, project grid, and word cloud were separate pieces, but they all lived in the same page lifecycle. That was fine when the only page was the portfolio. Once I wanted posts, feeds, routing, and reusable page structure, the old setup started to feel brittle. I could keep adding conditions and one-off scripts, but then the portfolio would get harder to design around every time I added something new.

Why a Seperate Blog Wouldn't Work

Initially, I wanted to leave the portfolio mostly alone and build a separate blog beside it. It seemed reasonable. The portfolio already worked, and the blog could be its own static Next.js site, similar to the system I use for VLViewer. Posts would be written in Markdown, published with frontmatter, and exposed through RSS and a JSON feed. The portfolio could then fetch the latest posts and show a small "Latest Writeups" section through a JSON file published to an R2 bucket. The separation was clean from an architecture point of view, but it was wrong for the user experience. I wanted moving between the portfolio and blog to feel like moving through the same site. A separate site would have had to recreate the dot background, duplicate the animation setup, and lose the route transitions I wanted. It also meant managing two copies of JavaScript or sharing files that carried a lot of unused code. It would have worked, but it did not fit the direction I had been moving in with VLViewer or with my work at Jolera. It solved the blog problem by splitting ownership in a way I would probably regret later. So I moved the site to Next.js. That was more work than placing a blog beside the portfolio, but it gave me one application, one routing model, and a cleaner way to bring React into the parts that benefited from it.

Adapting to React

The existing website already had a lot of imperative JavaScript. The canvas background mutated directly. The panels had their own positioning behavior. The project grid and word cloud initialized themselves against real DOM nodes. That code was written for a page that loaded once and then stayed alive. React expects a different contract. The design mismatches showed up in a few places. The first was hydration. React expects the DOM it hydrates to match what it rendered. The old scripts expected to start immediately and mutate the page. When those scripts ran too early, React saw a different DOM than expected. One example of this was the background system attaching to a canvas that React had already replaced. The fix was to make the boundary explicit. React renders the structure first. After hydration, a client component loads the existing script chain. That lets the older imperative code start only after React has claimed the page.

Navigation caused another version of the same problem. The first site was designed as a single page, so the background could live there without thinking about routes. In the new app, moving from / to /blog and back remounted page-level content. Since the canvas lived inside that content, the experience felt almost the same as jumping between separate websites. So I moved the canvas into a persistent site wrapper. Pages change underneath it, but the canvas stays mounted. React owns the routes and page structure. The original JavaScript engine still owns the canvas. Some smaller lifecycle problems came with that. When I navigated back to the portfolio, DOM-driven widgets had to initialize again because React had recreated their containers. But they could not run blindly. That would create duplicate listeners, stale intervals, and widgets fighting each other. The solution was guarded initialization. Global systems start once. Page-specific systems run only when the relevant DOM exists and has not already been set up.

Keeping the Parts That Worked

I did consider rewriting everything in React. It would have been cleaner in a very superficial sense, but it would not have been a good use of time. The background system was already tuned. The performance behavior already worked. The visual identity already existed. Forcing that into React's rendering model would have introduced risk without changing much for the person using the site. Keeping that code also preserves flexibility. The same JavaScript can still be reused in non-React environments. Unless I wanted a new visual identity, rewriting it all would have been more about satisfying a framework migration than improving the site.

Adding the Blogging System

Once the architecture was unified, I was able to reuse the same blog system I created for VLViewer.com. Posts are Markdown files with frontmatter for metadata like title, date, description, and category. At build time, those posts become static pages. The blog index can list them, individual post pages can render them, and the portfolio can show recent writeups without making a client-side request on first load. The site also generates JSON and RSS feeds through CI. That lets the writing exist outside the site itself and avoids the CORS friction I would hit if the portfolio tried to pull content directly from GitHub Pages.

Integrating Local LLMs

Near the end of the rebuild, I saw an opportunity to make the article feel more interactive without turning it into a larger platform feature. I have always been interested in local AI and since we are finally at a point where in-browser local LLMs are becoming feasible, I added a chatbot using Chrome's Prompt API. That fit the direction of the site. The interaction stays close to the user, avoids adding my own backend, and feels like part of the page instead of a separate AI service bolted onto it. However, the limitation is that the Prompt API is not a universal solution. It depends on browser support and on the user's device being able to run the local model well. I could have built a more browser-agnostic local setup, similar to the ONNX Runtime approach I used for my image upscaling website, but that would have added more complexity and required the user to download models they may not be able to use.

So I added prefilled prompt links alongside the local chatbot. Supported users can try the AI experience directly on the site, while everyone else still has a way to start the same kind of conversation in the AI tool they already use. For this project, that felt like the right trade off. The local chatbot makes the page more interactive where the browser supports it, and the prompt links keep the idea accessible without forcing the whole feature to depend on one experimental API.

What Changed in How I Build

The first version of mcallbos.co showed that I could build something polished and memorable. This version is less about proving that and more about keeping the work maintainable. I now care more about what happens after something ships. Can I extend it without duplicating logic? Can I debug it when the lifecycle changes? Can I add content without touching unrelated code? Can I preserve good work instead of replacing it just because I am using a different framework? Those questions are less flashy than a new design, but they matter more over time. That is one of the bigger lessons I have taken from real projects and automation work. A working system is not automatically a starting point for a rewrite. Sometimes the better engineering decision is to keep the strong parts, make the ownership boundaries clearer, and build around them.

TL;DR

mcallbos.co is now both a portfolio and a place to write about the work behind the work. The original version still matters because it gave the site its identity. The new version matters because it gives that identity somewhere to go next. That feels like a better representation of where I am now as a developer. I still care about polished interfaces, but I care more than I used to about maintainable systems, clear ownership boundaries, and reducing friction for users and for myself.

© 2026 Miles Calloway. All Rights Reserved.

FPS: 0
0.4
150
1000