Thanks for the suggestions!
@nobiot thanks! I tried @akashp’s timer code instead of modifying source files. 
@akashp here is the profiler report from moving into and out of a heading with ~30 backlinks. I’m not sure how to track down the anonymous lambda.
333,186,153 95% - org-roam-buffer--redisplay-h
333,186,153 95% - org-roam-buffer-persistent-redisplay
332,873,908 95% - org-roam-buffer-render-contents
332,482,810 94% - org-roam-backlinks-section
326,649,381 93% - org-roam-node-insert-section
288,534,976 82% - org-roam-preview-get-contents
275,521,735 78% - org-indent-refresh-maybe
275,263,383 78% - org-indent-add-properties
251,362,967 71% - org-at-item-p
245,778,327 70% - org-element-at-point
231,278,663 66% - org-element--parse-to
146,862,651 41% + #<subr F616e6f6e796d6f75732d6c616d626461_anonymous_lambda_38>
3,933,488 1% + org-element--cache-put
123,264 0% + org-element-org-data-parser
145,728 0% derived-mode-p
475,496 0% org-indent-set-line-properties
1,056 0% + org-current-level
1,056 0% + org-get-limited-outline-regexp
9,846,730 2% + org-mode
1,361,384 0% + set-auto-coding
363,288 0% + custom/org-roam-preview-default-function
272,984 0% + org-element--cache-after-change
205,737 0% + org-roam-strip-comments
97,336 0% + org-indent-notify-modified-headline
34,288 0% + org-fold-core--fix-folded-region
13,648 0% + #<compiled -0x1c692097ad1ad977>
22,766,044 6% - org-roam-fontify-like-in-org-mode
13,548,469 3% + org-mode
8,940,166 2% + font-lock-ensure
226,624 0% + #<compiled -0x1c692045bbe6dd77>
155,160 0% + mapconcat
23,888 0% + magit-insert-section--create
16,656 0% + magit-insert-section--finish
16,576 0% jit-lock-after-change
9,344 0% + magit-insert-heading
5,666,428 1% + org-roam-backlinks-get
10,169 0% + magit-insert-section--finish
4,144 0% jit-lock-after-change
353,242 0% + org-roam-mode
28,912 0% + magit-insert-section--finish
4,144 0% erase-buffer
312,245 0% + org-roam-node-at-point
14,612,032 4% + command-execute
1,599,015 0% + timer-event-handler
874,638 0% + redisplay_internal (C function)
8,288 0% winner-save-old-configurations
8,192 0% + ...
6,144 0% + evil-escape-pre-command-hook
1,008 0% + gcmh-register-idle-gc
688 0% + flycheck-display-error-at-point-soon
104 0% + eldoc-schedule-timer
84 0% + eldoc-pre-command-refresh-echo-area
Your delay timer override makes a big difference. I set the idle timer to 1 second (5 was quite long) and I can now navigate around with evil keys as I would expect. I’ll keep it like that for a while to test a bit more rigorously, but this definitely seems like a satisfactory solution most of the time. I have a few nodes with >100 backlinks (which is probably mainly sloppiness on my part), and those take quite a long time, 20+ seconds, to render the backlinks. A cache would be useful in these cases, but I’m on the occasions I need to actually work with those nodes I can probably just open a dedicated roam buffer to keep it from getting regenerated all the time.