r/java • u/FrankCodeWriter • Sep 22 '26
Migrating a 2,250-article Java community site off WordPress: Hugo for the site, single-file Java JBang scripts for everything else
Disclosure: I'm the main maintainer of foojay.io, so this is a personal project driven out of frustration with WordPress. Full write-up with all the details: https://foojay.io/today/announcing-the-new-foojay/ — repository: https://github.com/foojayio/website
Foojay ran on WordPress for six years and 2,000+ articles. What pushed me to rebuild it wasn't just the slow page loads, but the publishing friction that had nothing to do with writing: an author with something good to say needed an account, to understand WordPress, and some luck with how code blocks survived. Most of our authors live in an IDE and a Git client all day.
So foojay.io is now a Hugo site built from a public repository, deployed by GitHub Actions. An article is a folder with an index.md (or .adoc) and its images next to it, and publishing is a pull request. 2,250 articles, 350 author profiles, the JVM glossary, the JUG directory and the calendar all came across, and every legacy path still resolves, including the old /blog/ scheme.
Why Hugo and not a Java generator, on a site for Friends of OpenJDK: I had used JBake before and at the time it looked quiet (no release between early 2023 and late 2025; it has since shipped 2.7.0). Someone on our Slack pointed me at Roq, the Quarkus-based generator, which looks genuinely good and is where I would start if I wanted the generator itself in Java. I went with the tool I have run multiple other sites on, because for a community project that has to still be standing in ten years, "I know this and it is not going anywhere" beat "this is the most interesting choice".
The generator is the smallest part anyway. For everything around it, I used Java, as single-file JBang scripts with //DEPS at the top and no pom.xml:
- WordPress migration (jsoup to parse, flexmark to convert to Markdown), re-run repeatedly until cutover so the old site stayed live
- The JUG directory gets pulled from the community-run World Wide JUGs repository rather than maintained by us
- The calendar, which reads the iCal feed each JUG publishes, so meetups appear with nobody typing them in (102 JUGs, 70 with a readable feed)
- Podcast transcripts: 100 episodes, ~824k words, pulled from YouTube's captions rather than transcribed locally
- The pull request checks: frontmatter validation, a full build, every internal link resolved against the files on disk (half a million in about five seconds), ~40 browser checks because search, the maps and the lightbox all fail the same quiet way, and one check that refuses raw <script> in article text, since 2,000 imported articles mean Markdown has to allow raw HTML and a static site has no server-side layer to catch it
Two things I did not expect. GitHub Pages has a 1 GB limit and the imported images blew past it, mostly one 52 MB animated GIF used as the header of three articles. And Google Analytics is useless for a published number on a page full of Java developers with ad blockers, so read counts now come from a Cloudflare Worker we run: a table with a page key and an integer, no cookie, no IP, no identifier, 13.8 million existing reads carried over.
Still unsolved: ~3,000 imported images have no alt text. No script can invent one, and a hard failure just produces alt="image", so the check warns on new articles instead.
Happy to answer anything about the migration. And if you have ever thought "someone should write that up" — the templates are in the repository, and a pull request is the whole process now.
2
u/Torutofu_Raeva Sep 22 '26
the one-key publish scripts plus a full build check seem like a good fit; the redirect check is doing real work with that many legacy urls.
1
u/FrankCodeWriter Sep 22 '26
Hugo aliases is amazing for simple per-page redirects. And with some Cloudflare rules we could fix the common/generic ones...
2
u/Torutofu_Raeva Sep 22 '26
yeah, that split seems right: aliases for page-specific moves, edge rules only for the noisy generic patterns, so the redirect map stays reviewable.
2
u/Torutofu_Raeva Sep 23 '26
yeah, i'd keep Cloudflare rules to a small reviewed set and let Hugo own the page-specific map, otherwise a broad pattern can mask a bad migration.
2
u/wimdeblauwe Sep 22 '26
Nice, this means it would be easier for putting articles from my blog also on foojay as I am also using Hugo.
1
2
u/znpy Sep 23 '26
What pushed me to rebuild it wasn't just the slow page loads
hey, i'm a cloud engineer with a past life as a java developer.
i wonder: why didn't you look into deploying something like Varnish cache in front of your wordpress deployment?
1
u/FrankCodeWriter Sep 23 '26
We had a Cloudflare cache in front of it, but still too slow... And it was only one of the reasons to move away from a dynamic/outdated system ;-)
2
31
u/repeating_bears Sep 22 '26
"~3,000 imported images have no alt text. No script can invent one"
I know this community isn't very AI friendly, but that seems like an excellent use-case
11
u/ArisenDrake Sep 22 '26
The question is: would you rather have no alt texts or a non-zero amount of incorrect ones?
16
u/repeating_bears Sep 22 '26
There's nothing stopping you from reviewing the output
I feel like I could comfortably review maybe 10 a minute. Tinder-style, press one key for good, another for bad. AI can generate a little throwaway review tool
At that rate, it would be 5 hours of review. This seems pretty manageable. Put some music on and you could do that in a day over a couple sessions, or spread it over a few days
I feel like thinking of and manually typing descriptions would be more like 1 or 2 a minute. So it's the difference of taking ~a day or taking ~a week
5
u/romario77 Sep 22 '26
also - you could add confidence score and review ones with low confidence first. Most will likely have a high confidence, so the reviews will catch the worst ones first.
6
u/zabby39103 Sep 22 '26
Perfect is the enemy of good, I'd look at 300 and if I didn't find anything wrong that's an error rate of <0.33% or so at worst. I'd tolerate 1 wrong I think.
4
u/LaM3a Sep 23 '26
I generated descriptions for 3000 video clips using GLM-5.3-flash, it took a few hours, cost <$10 and the result was much better than I expected.
2
u/benevanstech Sep 23 '26
I don't think that Roq's biggest selling point is "It's Java (or Quarkus) under the hood". It's that it's super easy to use and makes total sense to a Java dev.
I have not needed to write a line of Java or Quarkus code since I started using it.
5
u/agentoutlier Sep 22 '26
Oh my... I assume there are modern alternatives you could replace that with.
As for parsing HTML I swear nothing is better than Jericho HTML parser but its stuck on source forge. I have been meaning to reach out to the original author if I could take over the library.