WRITING

From Bolt.new to WordPress: Rebuilding Null Expected

How I built my website with Bolt.new, why I eventually moved it to WordPress, and how I used AI and the native block editor to keep the custom design I originally wanted.

In August 2025, I started building Null Expected with Bolt.new. I wanted a personal website where I could publish my writing, showcase other projects and have complete control over how everything looked. I didn’t want to pick a ready-made theme, change its colours, replace the sample images and call it finished.

Bolt was appealing for exactly that reason. I could describe what I wanted, generate a working React application and keep refining it. Layouts, colours, typography, navigation, responsive behaviour and individual components were all open to modification. If I wanted something different, I could change the code, often with AI doing much of the implementation.

The result was a proper web application, built with React, TypeScript, Tailwind CSS and Vite, with its source in GitHub and deployment through Vercel.

It worked. I was pleased with what I could build, particularly without having to develop every component from scratch.

By August 2026, though, I was rebuilding the website in WordPress.

The original technology was still capable of doing everything I needed. What had changed was my tolerance for the amount of application maintenance involved in operating what was, increasingly, a content-focused website.

The WordPress rebuild introduced an interesting challenge of its own. I was moving to an established CMS partly to simplify things, but I still wanted a distinctive design. And I initially wanted to achieve that on WordPress.com’s Free plan, which restricts several site-wide customization features.

I ended up using a free block theme called Assembler, custom WordPress block patterns, templates, template parts and quite a lot of AI-assisted configuration.

I got the website I wanted, although the route there was considerably less straightforward than selecting a WordPress theme.

This is how the two implementations worked, what pushed me to reconsider the architecture, and where the tradeoffs ended up.

Starting with design freedom

Null Expected began as a personal project. I work in software quality and technical delivery, but I also enjoy building things, experimenting with technology and occasionally spending more time on a detail than that detail objectively deserves.

A personal website seemed like a reasonable place to indulge that.

I wanted somewhere to publish technical writing and develop my own visual identity. It needed to be recognisable, a little unconventional, and flexible enough to change as the project grew.

Most conventional website builders offer some degree of customization, but their design systems still impose constraints. You inherit typography scales, spacing decisions, component behaviour and layout conventions, and then work within whatever customization controls are exposed.

Sometimes that is perfectly sufficient. In my case, it was part of what I wanted to avoid.

Bolt.new gave me a much more open starting point. Instead of choosing a finished website template, I could generate and modify the application itself.

The first implementation became quite elaborate.

I used Inter for typography, indigo and purple as the main brand colours, gradients for some interactive elements, custom iconography, responsive grids and a collection of cat illustrations that became the project’s mascot.

Two views of the original Bolt.new website: a homepage with the cat mascot and purple branding, and a consulting page with three service cards.
The original Null Expected website built with Bolt.new and React, showing the homepage and consulting page.

I also documented the design system, partly because it was useful when requesting further changes from AI. Having precise colours, component styles and spacing rules was much more reliable than repeatedly asking for something to look “a bit more like the rest of the website”.

For example, the original CSS included a set of design tokens:

:root {
--btn-bg: linear-gradient(
90deg, #1e1b4b, #6b21a8
);
--btn-hover: linear-gradient(
90deg, #312e81, #7c3aed
);
--btn-text: #ffffff;
--icon-bg: #312e81;
--brand-gradient: linear-gradient(
90deg, #1e1b4b, #6b21a8
);
}

Source: src/index.css, original Null Expected repository.

The same file defined typography, animations, article formatting, responsive behaviour, navigation and the styling of other components.

There was even configuration for seasonal themes. Switching between the default design and alternatives such as winter or Pride involved changing the active theme configuration and rebuilding the application.

This was one of the more enjoyable aspects of the project. The visual design wasn’t restricted to whatever options a CMS or website builder happened to expose.

For the original purpose, the approach made sense. The question was what happened as the content grew.

The website underneath the design

It would be inaccurate to describe the Bolt implementation as a simple collection of AI-generated pages.

By the time I migrated away from it, Null Expected had developed a fairly substantial architecture.

The core stack was React 18, TypeScript, Vite and Tailwind CSS. React Router handled navigation. The marked library converted Markdown content into HTML, Mermaid supported diagrams, and React Helmet was used for page metadata.

The website also had custom code for article loading, filtering, categories, featured posts, author information, responsive article presentation and social sharing.

The content itself lived inside the repository.

Rather than using a content management system, I stored blog articles as Markdown files, organized by author and publication date.

The structure looked approximately like this, with the main application and content directories separated:

Null-Expected/
├── public/
│ ├── images/
│ ├── og/
│ └── Null-Expected-Icon-Pack/
├── src/
│ ├── components/
│ ├── config/
│ ├── data/
│ │ └── posts/
│ │ └── author1/
│ │ ├── 2025/
│ │ └── 2026/
│ ├── pages/
│ ├── utils/
│ │ └── blogUtils.ts
│ └── index.css
├── scripts/
│ ├── build-blog.mjs
│ └── prerender-pages.ts
├── tests/
├── package.json
└── vercel.json
Architecture diagram showing Markdown articles feeding React components and static build scripts, with GitHub Actions and Vercel handling deployment.
The original React publishing architecture, showing how Markdown content was processed through separate rendering and static-generation paths before deployment.

Each article had frontmatter containing the information the application needed to display and categorize it.

This is an actual example from one of my posts:

---
title: "Could You Reconstruct Last Tuesday's Release?"
excerpt: "The CRA's 24-hour reporting clock exposes a practical problem: how quickly can you establish what was actually released?"
tags: ["qa-processes", "industry-trends", "featured"]
author: "author1"
date: "2026-08-07"
readTime: "9 min read"
slug: "could-you-reconstruct-last-tuesdays-release"
---

Source: src/data/posts/author1/2026/08/could-you-reconstruct-last-tuesdays-release.md.

Publishing was a source-code operation. I created a Markdown file, populated its metadata, wrote the content and committed the changes.

The repository’s publishing instructions also used a filename convention for drafts. A file prefixed with draft- was excluded by the client-side blog loader; removing that prefix made it eligible for publication.

This is a perfectly legitimate way to run a technical blog. Markdown is portable, the content can be version-controlled, and Git provides a history of changes.

For someone who prefers working in an editor, it can be a very good arrangement.

But Null Expected was no longer simply a technical experiment. I was publishing regularly, updating older material, maintaining book and portfolio information, managing metadata and occasionally changing how the website presented its content.

The distinction between editing an article and modifying the application started to matter more.

A publishing system I had effectively built myself

The blog was doing several jobs that a conventional CMS would normally handle.

One utility, blogUtils.ts, discovered Markdown files, parsed frontmatter, converted their contents into HTML and created post objects for the React components.

The relevant part of the loader used Vite’s file-import mechanism:

const modules = import.meta.glob(
'../data/posts/**/*.md',
{
eager: false,
as: 'raw'
}
);

The same utility contained logic for ignoring draft files, resolving authors, mapping tags into categories and sorting articles.

Separately, scripts/build-blog.mjs generated HTML for articles and the blog index. It also produced canonical links, Open Graph metadata, structured data and a sitemap.

The production build combined several operations:

{
"build": "vite build && node scripts/build-blog.mjs && tsx scripts/prerender-pages.ts"
}

These additional steps existed for reasonable purposes, including producing HTML and metadata that would be available without depending entirely on client-side rendering.

However, they also introduced multiple places where the same content could be interpreted differently.

A good example is article metadata.

The React-side content loader, in src/utils/blogUtils.ts, reads the article excerpt directly from its frontmatter:

excerpt: frontmatter.excerpt || '',

Meanwhile, the separate static generator, in scripts/build-blog.mjs, uses a different field:

const metaDesc =
data.description ||
"A QA thought hub. What did you expect?";

Both scripts process the same Markdown content, but they expect different metadata fields. An article can therefore have a correctly populated excerpt in the React application while the generated static metadata falls back to generic text.

It’s a small inconsistency, but a concrete example of what can happen when content-processing responsibilities are implemented in more than one place.

Author information had a similar discrepancy. The client-side code could resolve author1 into the author’s actual name using a central configuration. The static generation script used the directory identifier directly when constructing its Article structured data.

These are not dramatic failures, but they illustrate how application-specific content management can accumulate inconsistencies as new functionality is added.

The build and deployment workflow had its own complexity. GitHub Actions included end-to-end, API and exploratory testing jobs, but the deployment job was not configured to depend on those jobs completing successfully. The repository even contained a commented-out dependency line.

Again, I am describing the configuration rather than claiming it caused a deployment incident.

What had started as an AI-generated website now had the concerns of an ordinary application: dependency updates, build processes, rendering consistency, metadata, publication rules, automated checks and deployment configuration.

None of that made React a bad choice. It simply meant I had taken ownership of substantially more infrastructure than I originally needed for a personal publishing website.

Reconsidering what I actually needed

By August 2026, Null Expected had become more focused.

The website had evolved from a broader QA thought hub into a personal portfolio and the home of my independent consultancy. I wanted to keep the writing, books, professional background and consulting information, but simplify how visitors moved between them.

The proposed structure became a relatively compact homepage supported by a writing archive.

At the same time, I wanted a more straightforward way to maintain the content.

I could have continued with React and introduced a headless CMS. I could also have refactored the existing content pipeline, consolidated the metadata handling and improved the publishing workflow.

Those were legitimate alternatives.

The reason I reconsidered WordPress was that I no longer needed most of the application-level flexibility for the main public website.

WordPress already had the things I was maintaining individually: posts, categories, tags, drafts, publication dates, revisions, media management and reusable article templates.

It also allowed the content to exist in a publishing system independent of the frontend application I had built.

There was an important security consideration, although it needs some qualification.

The original React website was not inherently insecure. A largely static deployment can have a relatively small runtime attack surface, and keeping its source in GitHub made the application inspectable and portable.

But generated code still needs reviewing. Dependencies need maintenance, content-rendering behaviour needs to be understood, and publishing controls need to work consistently.

For example, the original application used Mermaid to render diagrams and configured it with securityLevel: 'loose'. That setting allows less restrictive diagram behaviour than Mermaid’s stricter modes. It is a configuration worth reviewing when handling externally contributed or otherwise untrusted content, although its presence alone does not establish a vulnerability.

There was also the question of content integrity: keeping metadata consistent, maintaining reliable image references, managing publication status and understanding which changes would affect the public site.

Moving to managed WordPress.com hosting shifted some of those responsibilities to the platform. It did not eliminate security or configuration risk, and WordPress has its own administrative and platform-level considerations.

What appealed to me was the prospect of having fewer custom systems to maintain for routine publishing.

There was just one rather large condition.

I still wanted the website to look like Null Expected.

Moving the content out of React

Before concentrating on WordPress design, I needed to move the articles.

That was a more substantial task than exporting a CSV or copying text between editors.

The original articles existed as Markdown files with application-specific frontmatter. WordPress expected posts represented through its own content model, with separate fields for publication status, authors, dates, slugs, taxonomies and media attachments.

The migration therefore involved generating a WordPress WXR XML import from the repository’s source content.

The initial migration work targeted 26 articles belonging to author1, deliberately excluding older content attributed to the separate author2 profile.

The transformation had to preserve or translate several different kinds of information.

Diagram comparing Markdown fields such as title, excerpt, tags, author and images with their corresponding WordPress post fields.
Mapping Markdown articles and their metadata into WordPress posts through a WXR import, including image and diagram content that required additional review.

The content conversion itself was only part of the work.

Images required separate handling. The initial migration package included 12 image attachment entries, with a later regenerated package recording 13. Seven image-containing articles were identified for review, and two articles containing diagrams or flowchart material required additional attention.

The difficulty with images is that a Markdown reference is not the same thing as an image stored inside WordPress.

A path that works in the original site’s public directory may have no meaning after migration. An external image URL may continue working while leaving the article dependent on an asset outside the new media library.

Even where the body of an article imports correctly, its visual content and metadata may need further verification.

Mermaid diagrams presented a different problem. The React application had explicit logic to identify and render Mermaid code blocks. WordPress did not automatically inherit that implementation simply because the article text was moved.

That is an example of content that was technically portable in source form but depended on application functionality for its original presentation.

I also had to consider permalinks.

The original static generation script constructed URLs using an author and date hierarchy:

/blog/author1/2026/08/article-slug

The WordPress implementation used a different structure for individual posts:

/2026/08/07/article-slug/

The /blog/ archive path remained, even though the page is visibly titled Writing.

Preserving an article’s slug does not necessarily preserve its URL. Anyone migrating an established content site needs to consider what happens to existing links, bookmarks and search results.

The original article URLs were redirected to their WordPress equivalents, and I verified that the redirects worked. This allowed existing links to continue resolving correctly despite the change in permalink structure.

By this point, the move was visibly more than a change of website theme. The content was moving between two fundamentally different models.

Building a custom WordPress website without a custom theme

WordPress is often associated with installing a theme and customizing whatever settings it exposes.

That wasn’t the approach I took.

I chose Assembler, a free block theme by Automattic with Full Site Editing support.

Assembler provided the foundation: compatibility with the Site Editor, native WordPress blocks and the template system. I used that foundation to construct my own layout.

The distinction matters.

I did not develop a new PHP-based WordPress theme, nor did I bypass the theme system. I kept a supported block theme underneath the website and used native WordPress mechanisms to determine how its pages actually looked.

I also avoided building the main public site around a third-party visual page builder.

Instead, I worked with the WordPress block editor, custom patterns, templates and template parts.

This was a different form of AI-assisted development from the Bolt implementation. Previously, I had used AI to generate React and CSS. Now I was using it to produce block structures and importable WordPress pattern files.

The resulting website was deliberately more restrained than the original.

I kept the cat mascot, the name, the indigo brand colour and the typography direction. But I moved away from much of the gradient-heavy and animated presentation towards white backgrounds, charcoal text, generous spacing, carefully aligned content and thin neutral rules.

I wasn’t attempting to reproduce the old website exactly.

I was carrying its visual identity into a different design, using a different technical system.

Diagram showing the Assembler theme supporting the Null Expected homepage, Blog Home Query Loop, Single Posts template and shared header, with WordPress managing posts and media.
The WordPress architecture behind Null Expected, using the Assembler block theme, custom homepage patterns, Blog Home and Single Posts templates, and a shared Header Template Part.

Patterns, templates and template parts

One of the more important things I had to understand was that these WordPress concepts are related but not interchangeable.

A page contains the actual content of that page.

A pattern defines a reusable arrangement of blocks. Depending on its configuration, it may be synced, shared with overrides, or independent after insertion.

A template controls the structure used to render a particular class of content, such as the blog archive or individual articles.

A template part is intended for shared structural elements, such as the header.

These distinctions determined where I needed to make changes.

The homepage was assembled from custom block-based sections. It contained the primary introduction, consulting information, selected writing, books and the About/Contact content.

The writing archive used the Blog Home template.

Individual articles used the Single Posts template.

The header was managed through a shared Header Template Part, preserving the existing cat icon while adding the Null Expected name and navigation.

The point of the architecture was to make the main content manageable without having to reproduce its presentation each time.

For example, I wanted articles to have consistent reading widths. That belonged in the article template rather than requiring me to adjust the same settings for every post.

Similarly, the shared header needed one source of truth. Changing the site name or navigation in an individual page would not have been the correct place to maintain it.

There is a learning curve here, particularly because the editor can make different structural levels look quite similar.

When something behaves unexpectedly, it matters whether you are modifying an individual block, an instance of a pattern, the original pattern, a template or a shared template part.

It is easy to understand why someone approaching WordPress from a conventional page-editor perspective could find that confusing.

Making the writing archive dynamic

The writing archive is one of the clearest examples of how the new architecture differs from the old one.

In React, I had a component that loaded article objects, held state for categories and tags, filtered the posts and rendered the results.

The WordPress equivalent uses a Query Loop block.

It retrieves posts through WordPress’s content model and renders them using nested blocks for the date, title, excerpt and navigation.

The archive was configured to display six posts per page, with a two-column editorial layout on larger screens and a single column on mobile.

The archive’s documented query configuration was:

{
"query": {
"perPage": 6,
"postType": "post",
"order": "desc",
"orderBy": "date",
"inherit": false
}
}

This is the query configuration, rather than the complete serialized block markup. The full implementation also includes the Post Template and its nested blocks for rendering individual articles.

The inherit:false setting is significant. It allows the Query Loop to use its explicitly defined query rather than simply inheriting the surrounding template’s query context.

That gave me a controllable archive structure and reduced ambiguity about what the block was supposed to retrieve.

There was no need to maintain a separate React component for this functionality, or write application-specific code to load article metadata.

The output also matched the newer editorial design better. The archive uses relatively simple text-based entries rather than relying on heavy boxed cards and decorative treatments.

Of course, WordPress’s Query Loop is not a replacement for every custom filtering feature that the React application supported. The tradeoff was acceptable because the public website had become simpler.

The Free plan customization problem

I initially wanted to build the WordPress version on the Free plan.

That introduced a constraint which is easy to underestimate if you are accustomed to modifying application code directly.

WordPress.com’s Free plan restricts site-wide customization through the Styles system, including custom colour palettes and typography settings. Custom CSS is not included by default either. These features had become available on the Personal plan in April 2026, but I initially wanted to see how far I could take the design using only the Free plan’s native block capabilities.

The Site Editor and native block tools remain available, but there is a difference between changing global design settings and styling individual blocks.

This was precisely the type of limitation that had made Bolt.new appealing in the first place.

I wanted to specify details such as the exact indigo used for accents, text hierarchy, alignment, border treatments and spacing. I didn’t want to be restricted to the theme’s default aesthetic.

The solution was to work within the block architecture rather than depend entirely on site-wide Styles.

I generated custom patterns using WordPress’s serialized block structure, with design settings applied to their constituent blocks.

These patterns could then be imported as JSON and used within the Site Editor.

The resulting homepage was built from nested native WordPress blocks, using Group and Columns blocks to control composition, alignment and spacing. The pattern structure carried much of the design configuration through block attributes and serialized markup, rather than relying on a separate stylesheet or a premium theme’s global styling controls. The saved pattern could be imported and reused, although subsequent structural changes sometimes required editing the pattern itself rather than simply modifying the page.

This approach gave me considerable control, but it wasn’t equivalent to having unrestricted custom CSS.

I was working with the capabilities exposed by individual blocks and the editor. Some controls were available directly in the interface; others became easier to reason about by inspecting the stored markup.

I also had to consider how much of the appearance was explicitly defined in a pattern and how much remained inherited from the theme.

That distinction would later matter when troubleshooting design changes.

The design work started on the Free plan, but I eventually upgraded to Personal when I was ready to move the published website to my existing nullexpected.com domain. I wanted to keep the convenience of WordPress.com’s managed hosting while using my own domain as the primary address. The domain remained registered with GoDaddy, but connecting it to WordPress.com required a paid plan.

The upgrade came with a second benefit. Personal already included Global Styles and custom CSS, giving me more flexibility to refine the design without working exclusively through individual block settings. Those features weren’t the reason I upgraded, and the core design had already been built using custom patterns.

So the finished website wasn’t entirely free, but the experiment had demonstrated something useful: I could create a distinctive design using the Free plan’s native tools, then move to an affordable hosted setup with additional customization options without having to rebuild it.

When a simple change stopped being simple

The new site made publishing easier, but it didn’t make every kind of maintenance easier.

The distinction between pages, patterns and templates mattered elsewhere too.

The Single Posts template needed careful control of content width so that article images remained within the intended reading area.

The header needed to be edited at the shared Template Part level, because changes there affected multiple pages.

The writing archive needed the correct query configuration and block structure rather than a visual approximation of a post listing.

These issues weren’t particularly exotic. They came from learning and working with the boundaries of the WordPress editing model.

One thing I found increasingly important was distinguishing content editing from structural editing.

WordPress patterns can deliberately limit editing to content, preserving the layout. That is useful when several people need to populate a consistent design without accidentally changing its structure.

But when I am the person maintaining both the content and the design, those restrictions can become inconvenient.

A pattern that is easy to insert isn’t necessarily easy to modify afterwards.

And while WordPress supports exporting patterns as JSON, that portability doesn’t mean all their behaviour will be identical in another theme or environment.

The blocks remain subject to supported attributes, theme styles and the editor’s interpretation.

There is some irony in moving from React to WordPress to avoid maintaining custom code, then occasionally needing to inspect serialized block markup to adjust the position or dimensions of an element.

The complexity hadn’t vanished. Some of it had moved to another layer.

The domain move was a separate piece of work

The original website was hosted through Vercel and connected to my custom domain, nullexpected.com.

The WordPress rebuild initially lived on a WordPress.com subdomain, which allowed me to develop and review the replacement independently before making it public under the main domain.

After the design and content were ready, I connected the existing GoDaddy-managed domain to WordPress.com.

That introduced the usual domain-management considerations: nameservers, DNS propagation, verification and selecting the primary site address.

There was also a practical limitation in the GoDaddy interface. The existing nameserver records weren’t editable in the DNS records table in the same way as ordinary DNS entries, so the change had to be handled through the appropriate nameserver management controls.

WordPress subsequently verified the domain connection, but that didn’t mean every visitor would immediately see the new website. DNS propagation and cached responses could still direct some requests to the previous destination for a while.

This was separate from the WordPress design work, but it was still part of the migration.

Moving content, replacing the frontend, connecting a domain and preserving old URLs are related operations, yet they have different mechanisms and potential failure points.

It is worth keeping them distinct when describing what the rebuild actually involved.

Comparing the two implementations

Looking at the original React application and the new WordPress site, I don’t think there is a universal winner.

The React implementation offered enormous flexibility. I could build whatever frontend behaviour I wanted, use custom components, alter the design system directly and keep everything under source control.

The project could also accommodate features that were applications in their own right.

It had a transparent development workflow, even if that workflow had grown more complicated than I initially expected.

WordPress offered a different set of advantages.

Content publication became a native activity rather than something dependent on creating source files and triggering a deployment.

Posts, categories, tags, media, revisions and article layouts belonged to the CMS.

The public website could remain visually distinctive without requiring me to maintain a custom React frontend for ordinary pages.

There were also compromises.

The WordPress.com plan determines which customization and extensibility features are available.

The block editor introduces its own abstraction and editing conventions.

Custom patterns can be awkward when a specific detail isn’t exposed through the editor, and their portability needs to be considered separately from the portability of the written content.

Some functionality from the React application is easier to implement in React than inside the native WordPress block system.

The most useful comparison is therefore between the particular requirements I had and the maintenance responsibilities associated with each implementation.

AreaOriginal React websiteWordPress rebuild
Visual controlDirect through components and CSSThrough native blocks, patterns and theme capabilities
Content creationMarkdown files and Git workflowWordPress editor
PublishingBuild and deployment processNative publication workflow
TemplatesReact componentsWordPress templates and template parts
Blog listingApplication code and article metadataQuery Loop
Shared stylingCSS variables and component stylesBlock attributes, patterns and available theme styles
Source ownershipFull application source in GitHubExportable content and patterns, managed platform
Application featuresBroad flexibilityDependent on block, platform and plan capabilities
MaintenanceFrontend, dependencies, builds and deploymentWordPress configuration, content and platform constraints

The React version was not technically incapable of doing the job. The original architecture was simply becoming disproportionate to what I wanted the public site to do.

WordPress reduced the amount of custom implementation I needed for publishing, while allowing me to retain considerable control over the design.

The deciding factor wasn’t whether one technology was more powerful.

It was where I wanted to spend my time.

AI was involved in both versions

One aspect of the project is easy to overlook when describing it as a move from Bolt.new to WordPress.

I used AI throughout both implementations.

With Bolt, AI generated and modified much of the React application’s code. I could experiment with components, request changes, develop styling rules and expand functionality without manually implementing every detail.

When I moved to WordPress, AI remained central to the technical work.

It helped construct the migration artifacts, generate importable patterns, configure block layouts, reason about template hierarchy and troubleshoot the awkward cases where the editor didn’t behave as expected.

The difference was the environment in which the generated work had to operate.

In React, AI could modify application source directly. The application would then be built and deployed as code.

In WordPress, AI-generated configuration had to fit the structure and behaviour of a mature CMS. A block’s attributes, a template’s inheritance rules or the difference between a synced pattern and an ordinary instance could determine whether a proposed change worked.

That imposed useful constraints, but it didn’t eliminate the need to understand the generated output.

In fact, several of the more frustrating moments came from apparently plausible instructions that didn’t match the actual implementation.

For example, some proposed changes targeted the page editor when the actual configuration belonged to a pattern or template. The suggested approach could sound reasonable while still targeting the wrong part of WordPress’s editing structure.

The generated answer was not necessarily grounded in the specific structure I had in front of me.

I increasingly needed to provide the actual markup, inspect the block hierarchy and verify the result rather than accept an instruction because it sounded technically reasonable.

That was true of the React code as well, particularly when multiple scripts or components handled related concerns differently.

AI made both projects more accessible. It also made it possible to create fairly sophisticated implementations without encountering every underlying technical decision during the initial development.

Those decisions still existed, and eventually some of them had to be understood.

Where Null Expected ended up

The current website is simpler than the original application, both visually and structurally.

The homepage presents the consulting work, selected writing, books and personal background. The writing archive is managed through WordPress. Article layouts are controlled by the Single Posts template, and shared elements such as the header are maintained through the Site Editor.

The design is still deliberately customized. The cat mascot remains, the indigo is recognisable, and the layout reflects the same attention to typography, spacing and alignment that made me choose Bolt in the first place.

I also like that the website no longer needs a frontend application change for routine publishing.

There are things I could do more easily with the React implementation, and some WordPress limitations are genuinely irritating.

If I were building a site primarily around interactive tools, highly dynamic frontend functionality or unusual application behaviour, React could still be the more appropriate foundation.

For the current Null Expected website, the balance favours WordPress.

I started with Bolt because I wanted to build something that looked like mine, without being constrained by an existing theme. I moved to WordPress because I wanted to keep publishing and developing the project without maintaining a bespoke content platform around it.

The second implementation required more customization than I initially expected, and its patterns and templates have their own maintenance implications.

But I can now make ordinary editorial changes in a system intended for that work, while keeping the visual result reasonably close to what I designed.

Null Expected will probably continue to change. It is a personal project as much as a professional website, and experimenting with its implementation is part of the appeal.

For now, I am content with having a website that I can publish to without treating every new article as another application deployment.


Technical references

The original application source is available in the Null Expected GitHub repository. The technical excerpts above are drawn from the repository’s main branch, including src/utils/blogUtils.ts, scripts/build-blog.mjs, src/index.css, package.json and .github/workflows/deploy.yml.

The WordPress implementation uses Assembler by Automattic, with native WordPress block patterns, Site Editor templates and pattern import/export.

WordPress.com’s documentation also describes the Styles plan restrictions that informed the original Free-plan customization approach.


← All writing

Discover more from Null:Expected

Subscribe now to keep reading and get access to the full archive.

Continue reading