PRIMUS candidate search
I rebuilt the candidate search recruiters used all day at PRIMUS, Navigos Group’s career platform for senior professionals. Profiles open 14x faster and repeat searches return instantly.
All work- Users
- Recruiters and consultants
- Rebuild
- React and Redux to Angular, 2020
- Trade-off
- A slower first load
A search recruiters ran all day
PRIMUS was Navigos Group’s career platform for senior professionals. It has since been discontinued. Recruiters and consultants worked in its Boolean candidate search all day: search, open a profile, load more results, go back to an earlier search.
Other developers started the React search page before I joined. It ran as a widget inside a PHP page, and every step went back to the network: results had no cache, profiles loaded on click, and numbered pages reloaded the list and jumped to the top. I worked on that page first, then rebuilt the dashboard in Angular in 2020.
Why a rebuild
Angular gave us one established setup for routing, state and UI components. The React app was spread over several packages, its state was fragmented and it felt slow in daily use. A rebuild let me fix code quality and speed together. The cache and list techniques below would also have worked in React.
Making the second search cheap
Results are cached by the normalized query, so “finance and director” and “finance AND director” share one entry. Going back to a search also restores how far down the list the recruiter had loaded.
The list is virtual and requests the next 50 rows when the recruiter is 20 rows from the end. Full profiles for the rows on screen load in the background, and other sections load their code a second or more after start-up, so they open without a network wait.
Measured side by side
I rebuilt both apps from Git, served them from one mock backend and ran the same search session five times on each, on a throttled slow 4G connection with a CPU slowed four times.
| Measure | React | Angular |
|---|---|---|
| Open a profile | 692 ms | 50 ms |
| Repeat a search | 899 ms | 16 ms |
| Waiting while browsing rows 51 to 200 | 2.6 s | 1.1 s |
| First result row on screen | 6.7 s | 11.0 s |
| Main JavaScript bundle, gzip | 471 KB | 913 KB |
| API requests per session | 33 | 224 |
What I would do differently
The first screen got slower. Angular showed its first result row at 11.0 s against React’s 6.7 s, and the profile prefetch sent one request per row. Today I would fetch those profiles through the batch endpoint the React app used and cancel prefetches when a new search starts.
After the first screen
- Profiles open 14x faster: 692 ms to 50 ms.
- Repeat searches return in 16 ms instead of 899 ms.