Skip to content

From Next.js to SSG: Rebuilding tradik.com

· 3 min read

tradik.com ran on Next.js with output: 'export'. That produced a static site, and everything about it was reasonable in isolation. Taken together it was a React toolchain, a node_modules tree of several hundred packages and a build step, all to publish nine pages that do not change between deploys.

The output was static. Nothing producing it was.

It now runs on SSG, the static site generator we maintain — a single Go binary. Markdown and YAML in, HTML out.

What the framework was actually buying us

Very little, on this site. Next.js earns its keep when you have routing that depends on data, incremental regeneration, or server components doing real work. tradik.com has a homepage, four marketing pages, three legal pages and a contact form. The React runtime shipped to every visitor existed to hydrate components whose output never varied.

The parts that did vary — the service catalogue, the product cards, the tool categories, the FAQ — were TypeScript arrays inside .tsx files. Editing a product tagline meant editing a React component. That is a strange place to keep copy.

What replaced it

Content is Markdown with frontmatter. The structured parts moved to YAML in data/, and the templates read both.

That split turned out to matter more than the file formats. The homepage FAQ and the FAQPage structured data behind it now come from the same six entries in data/faq.yaml. Before, they were a JSX block and a separate array passed to a <FAQSchema> component — two lists that had to be kept in step by hand, and the kind of thing that silently drifts until a rich result advertises an answer the page no longer shows. The same is true of the product cards and their ItemList.

The build says no

The interesting difference is not the generator. It is that the build now enforces what used to be a review comment:

check_links: strict     # a dead internal link fails the build
check_images: strict    # an image with no alt attribute fails the build
check_meta: warn        # title and description length, advisory
check_orphans: warn     # pages nothing links to

check_links caught a stale /blog/ reference in the footer within a minute of being switched on. check_orphans caught the tag archives the generator creates automatically and that nothing linked to — pages a crawler would find and a reader never would.

What a visitor gets

No framework, no hydration, no render-blocking JavaScript. The only script is a few kilobytes of progressive enhancement for the mobile menu and the contact form, and the site works with it switched off: the form posts normally and a Cloudflare Pages Function answers, redirecting to a real confirmation page rather than to a query string only a script could read.

What we kept

Every URL. The old export served /services; the build now serves /services/, and Cloudflare answers the extension-less form with a redirect, so nothing that was linked or indexed has moved. The pre-2019 WordPress paths that still attract crawlers — /press-room, /category/*, the dated archives — are answered explicitly rather than left to 404.

What it cost

A day, most of it spent on content rather than code, and two bugs found in our own generator along the way — both fixed in 1.8.21 before this site shipped. Dogfooding works.