Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 88788148df903019ffbc5b7d8b3dca7d1d759712)
+++ src/services/ActivityPubService.js	(revision 2d6a9c360f54275fa0b6256c64c88bf2d5782554)
@@ -1464,4 +1464,12 @@
 }
 
+// Bake a post's raw source into its display HTML (the ActivityPub `source` model): done ONCE
+// at save and cached in posts.content_rendered, so page views serve it statically instead of
+// re-linkifying every render. Step 1 = #hashtags + bare URLs (cheap, no network). Step 2 will
+// resolve @mentions here too (webfinger once at save instead of per page view).
+export function bakePostContent(source) {
+  return linkifyBody('', source || '');
+}
+
 // Extract the AP Hashtag tag objects from already-linked reply content.
 function hashtagTags(base, content) {
@@ -2463,4 +2471,4 @@
   deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
   getReplyUris, markNotificationsSeen, countUnseenNotifications, hasPlayableAudio,
-  linkifyBody, listFollowers, removeFollower,
+  linkifyBody, bakePostContent, listFollowers, removeFollower,
 };
