Published Monday, November 17, 2025 by mjac
Topics: Web design

NextJS Redesign for 2026

I loved using Ghost, it is a great platform, and I'd recommend it to anybody running a newsletter.

I've moved this website off the selfhosted ghost platform. It's now a very simple, custom react nextjs website. Ghost was fun, but they've undergone a lot of change since I last deployed. Most notably a new focus on ghost as a platform for federated content in the fediverse.

Reasons for this change are simple.

0. Inspiration

I felt like it. I barely remember when I used to be inspired to write, on my website and other platforms. Can I recapture that inspiration?

1. Portability

For years I've used Obsidian.md to manage my personal and work life. To great benefit. In particular, all data associated with obsidian workflows exist as simple markdown text files. It just does not get more portable than that. Now, authoring posts in ghost, and having posts be trapped in a ghost application database, feels like unwanted friction.

2. Accessibility

If I author posts within Obsidian, and have a route to directly publishing those posts from the Obsidian vault directly to my website, then I can easily author in the same platform where I'm doing all my other thinking and task management. It's right there. Not off on a website somewhere.

3. Maintainability

Having a docker container running ghost on a virtual server adds some maintenance overhead.

  • Keep the server up to date and secured
  • Keep the ghost application containers up to date
  • Maintain backup policies and procedures for the ghost mysql database, the ghost "all data" import/export files, and the media content directory.

The new workflow is ephimeral. I run a command to build and deploy a new docker container whenever I want to publish new content. If the server blows up, there's no data to be lost. If it crashes, it can be rebuilt in mere minutes from source.

4. Security

One less internet connected database driven application server to worry about. Replaced with a statically generated nextjs server instead.

The Visual Design of mjac.dev

I wanted to simplify the design even further from my previous custom ghost templates. The design process took a single saturday afternoon:

  • Use coolors.co to choose a color pallette that feels OK without needing a light/dark mode toggle.
  • Choose a few simple but fun fonts.
  • With affinity studio create a simple mock up with those colors of the home page. As dead simple as possible, clean, lots of white space, but a bit of color and shape so it's not just immediately boring.
  • Implement design as a layout in nextjs using tailwindcss.

The NextJS Implementation

This part took a few days instead of a few hours, because I set some goals to force me to improve my react implementation skills.

  • 100% test coverage
  • Build and publish automation
  • Take advantage of all the enterprise react design patterns from the start:
    • Abstracted configuration
    • The provider pattern for data loaders, even though it's parsing local markdown files instead of making API calls, with fully modular implementations and automatic test mocks.
    • The services pattern, where all hooks based on the provider and their caching logic are abstracted with a defined interface

Following these patterns leads to some very easy to read code, even when the SSR benefits like caching at the service level instead of the component level are as non obvious as everything else SSR with nextjs. For example:

import ListPosts from "@/components/ListPosts";
import SectionHeader from "@/components/SectionHeader";
import { getAllPosts } from "@/services";

export const postsPageMetadata = {
  title: "All Posts - mjac's waste of space",
  description: "List of all posts to this blog.",
};

export default async function PostsPage() {
  const posts = await getAllPosts();
  return (
    <main className="container mx-auto p-4 py-8">
      <SectionHeader className="mb-8">All Posts</SectionHeader>
      <ListPosts posts={posts} />
    </main>
  );
}

You're on the right track when adding comments would just make the code less legible.

Deployment failed with Vercel

I considered canning my docker server and publishing this directly with the free tier of Vercel. But I ran into some weird build issue, where vercel's post build tooling was failing on it's own mkdir calls for creating it's cache artifacts or something. I couldn't correlate this error with anything from my own codebase, and I couldn't find others reporting a similar error with search, or by asking copilot for help.

In the end, I just dug in and created a docker build and deploy process to ship a built container to a droplet docker server. That's more satisfying than worrying about a free tier Vercel deployment anyway.

# After successful run of next build...
Error: ENOENT: no such file or directory, mkdir '/vercel/output/static/attachments'

My best guess for this error, is has something to do with the next/image <Image> component when rendered within markdown output as having string URLs for the image sources that point to /public/attachments, which is itself a symlink to a folder inside an Obsidian vault in the project folder. I would guess when the vercel cli copies the project to it's build server, it's not following that symlink and the attachments folder is missing. Maybe. At this point, I'm not interested in investigating further.

Improvements?

Is this an improvement over the old Ghost site? I hope so. If I have more fun, and an easier time making updates, then great. Who is ever going to look at this thing anyway, besides me and clanker crawlers.