Join Nostr
2026-09-28 13:36:35 UTC

World Collider on Nostr: Single-Page Applications, i.e. webpages rendered with reactive framework such as Vue ...

Single-Page Applications, i.e. webpages rendered with reactive framework such as Vue or React have one massive pain point in performance - rendering tremendous amount of elements. Unfortunately this exact situation is the spotlight of a social network and/or chat application such as Pleroma - it has to display a lot of posts in timelines and threads. To make matters worse - Mastodon API, which PleromaFE is using, doesn't paginate thread data https://docs.joinmastodon.org/methods/statuses/#context so if you're opening a very long thread, such as group DM thread or a "hellthread", there is a massive lag to the entire page, and in worst case - browser itself, because UI gets burdened with a task of rendering and displaying hundreds of posts at the same time. As an added bonus - doing anything reactive that touches these posts also induced extra lag.

Thankfully, solution exists - Virtual Scrolling, which boils down to a simple concept - "just render what's on screen (and stuff immideately outside of it)", and there's an existing solution for us - "vue-virtual-scrolling" npm module, we're even using it already for emoji picker. But uh-oh! Using it has several serious drawbacks, all because it does a lot of DOM reuse and doesn't care about items' state, this breaks a few things.
- If you had media playing in an attachment and scrolled away from it - media suddenly stops playing.
- If you opened a reply form and scrolled away - it gets closed unceremoniously and doesn't even give a chance to save a draft.
- If you expanded conversation it gets forcibly collapsed, all of the mentioned cases now also apply to any posts in this expanded conversation.
That being said, the whole "expanded conversation" is another thing that "vue-virtual-scrolling" doesn't do - nested virtualizing. Yeah. In PleromaFE you can instantly expand a post to see its context right then and there - inside a timeline, no need to navigate to another page. This complicates things further because now we need to do scroll virtualizing on two independent levels - virtualize the timeline and virtualize the nested expanded conversation - we can't skip it because said conversation might be 100 posts long!

We had to implement our own scroll virtualization, localized in a useVirtualScrolling composable https://git.pleroma.social/pleroma/pleroma-fe/src/branch/develop/src/composables/useVirtualScrolling.js . The core idea remains the same - only items that are on screen are rendered, everything else gets replaced by a spacer, as scroll moves items come and go and spacers get adjusted. Main distinguishing feature of it - being able to "suspend" virtualization - a post can say "hey, use opened up reply form, exclude me from virtualization" - and the post will always be rendered as-is, not replaced, spacers will be put above and below it to fill the gaps. Another distinguishing feature is ability to disable "scroll compensation", to account for nested virtualization case. This feature is a bit janky and to be honest incomplete, as the whole "nested virtualization" has proven to be anything but trivial. Long story short - there is a case where we don't know the real height of an item yet, or real height changes (i.e post got edited off-screen), if this happens when item moves into view from above the entire list shifts, making it very jumpy and unpredictable. In this case we need to be compensate for it - scroll in the opposite direction by the "jump" amount. Having nested virtualization means that it's very likely that both layers try to compensate at the same time, making UI even jumpier, so we have to disable this "feature" on one of the levels. I wish this would be where it ends but it's not as trivial as that, and it still needs improvements. I'll get to it eventually.

I should mention that we had an older implementation of virtual scrolling that was horribly imprecise, only applied on timelines (not on conversations) and had major problems when timeline contained a lot of muted or hidden posts. This got removed entirely.

In the end:
- Timelines have proper virtual scrolling - navigating between pages is much faster now, everything is more responsive and there's less lag as a result of UI only rendering few posts.
- Conversations and Chat Views also have virtual scrolling too. Opening long threads is much faster now, browsing long threads also doesn't lag the entire UI anymore.
- There is a bit of a added bonus of scroll position persisting when expanding a thread - post you've expanded stays in view (mostly)

What remains to be done on this front:
- Remove remaining jank
- Add virtual scrolling to notifications
- Remove various leftover optimizations done to reduce the lag caused by lack of virtual scrolling.