All work
A recruitment job board where every vacancy gets its own page
Bullhorn RESTWordPressNext.js 16React 19TypeScriptTailwind CSS v4
170%More job interactions and landings on individual vacancy pages
37Automated tests, four suites of them on the public request surface
8Schema.org types emitted, including JobPosting
- Vacancies were published through an embedded Bullhorn listing, so no individual role had a URL of its own and none could be indexed, linked or shared.
- Searches combining a job title with a location had no page to land on, which left the long tail of recruitment search unaddressed.
- The front end ran a multipurpose WordPress theme with a page builder, measuring 10s LCP on desktop.
- Vacancy presentation lived inside the embed rather than in WordPress, so changing it was a development task.
- Split the concerns rather than replacing everything: Bullhorn stays the system of record for vacancies, WordPress stays the editorial surface, and Next.js owns rendering.
- Built a WordPress plugin as the adapter layer. It exposes a card-shaped REST namespace for the front end, locks the rest of wp-json behind authentication, and rewrites media URLs to the CDN.
- Every vacancy and post became a cached route, revalidated on demand the moment WordPress fires its webhook, with the secret compared in constant time.
- Structured data on every page type, generated from the same typed object the page renders, plus a sitemap index with dedicated children for pages, blog, jobs and taxonomies.
- Every vacancy is a page with its own title, description and JobPosting markup, eligible for the Google Jobs experience.
- Publishing in WordPress refreshes exactly the affected pages within seconds, with no rebuild and no deploy.
- Content editing did not change for anyone. The team still writes in WordPress, and still uses Gravity Forms.
Technical detail
- Revalidation is tag-based and the tag names are shared between both codebases. The plugin sends the same identifiers the front end registered its fetches under, so one changed post refreshes the list, the single page and its taxonomy archives -- and nothing else.
- The plugin locks every wp-json namespace behind authentication except the one the front end reads. That allows anonymous blog reads while keeping Gravity Forms and the admin REST surface authenticated, which is the combination the usual all-or-nothing approach cannot give you.
- Card responses carry a weak ETag derived from post IDs and modification times, so an unchanged list costs a 304 rather than a payload.
- The Bullhorn apply endpoint is proxied through the app rather than called from the browser, which keeps the corp token server-side and gives a single place to enforce honeypots, body-size limits and rate limiting -- distributed through Redis where available, in-memory where not.
- Every apply attempt, successful or failed, is logged back to a custom table in WordPress, so the recruiters who live in that admin can see submission history without anyone opening a log aggregator.
- Performance is gated rather than asserted: Lighthouse runs in CI against eight representative routes on a desktop profile, scoring performance, accessibility, best practices and SEO, so a regression shows up on the pull request that caused it.
- The blog data source is switchable between the plugin API and core wp/v2 through one environment variable, with a mapper normalising both to the same card shape. The migration to the plugin shipped incrementally, with the old path always one variable away.