Job aggregator admin panel
Around forty managed entities: vacancies, companies, articles, events, form builders
- React
- TypeScript
- MobX
- Formik + Yup
- +3
Regional job aggregator: legacy-to-Next.js migration focused on SEO
A job search platform with vacancies, resumes, applications, chats and career events. I moved the product from a legacy SPA to the Next.js App Router and built its SEO layer — from server-rendered metadata to a sitemap generated from the API.
Context
A regional job aggregator: applicants maintain resumes and apply, employers publish vacancies and process applications, and on top of that there are career events, articles, courses and an event map.
By the time I joined the product was already live, but written as a classic SPA: all content rendered on the client, metadata injected through react-helmet, and search engines saw an empty document.
Problem
For a job aggregator organic search is the main channel: every vacancy and every company page has to be indexable. In the legacy version that simply did not happen.
The second problem was speed. The first screen waited for the bundle and a chain of client-side requests, which was clearly noticeable on weaker devices and mobile networks.
The third was maintainability. Metadata logic lived in configuration objects next to page descriptions; canonical and noindex were set by hand and were easy to lose during changes.
Solution
I moved the application to the App Router and server-rendered the key sections: vacancy and resume lists, vacancy, company and applicant pages arrive as ready markup. Metadata is derived from the same server request that fetches the page content.
Metadata is described through generateMetadata: title and description are assembled from real entity data — position, salary range, city, the number of open vacancies at an employer. For list pages the description includes the current number of offers so the search snippet is not boilerplate.
The sitemap became a route that asks the API for the identifiers of all public entities and groups them by section. A new vacancy enters the sitemap automatically, with no manual edits.
Public URLs are human-readable: users and crawlers see transliterated paths such as /vakansii and /soiskatel/:id, while the app uses latin routes internally. The mapping is described by rewrite rules — about seventy of them.
On the product side I built Mattermost-based chats with a WebSocket client, applications, SMS and email authentication, VK sign-up, career-island landing pages for offline events and an event map on Leaflet. Errors go to Sentry, behaviour to Yandex.Metrica with session replay.
Outcome
Vacancy and company pages became indexable, and the sitemap stopped being manual work. The first screen arrives from the server, so the main content is visible before JavaScript loads.
Metadata stopped being scattered settings: it is assembled in one place from entity data, and adding a new section no longer requires remembering a dozen small fields.
I carried the experience of this migration into this portfolio: the SEO layer here is built the same way, but with a single metadata factory that makes it impossible to forget canonical or language alternates.


Around forty managed entities: vacancies, companies, articles, events, form builders
A rental and purchase portal built from scratch on Next.js: map, geocoder, booking, mortgage
Listing moderation, management of agency properties and mortgage programmes