Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 7f46817d08f908b01fdf536e5e4935d81fb81c84)
+++ src/routes/posts.js	(revision d54dadef21ca21f9a76468bde33d1e745e9f3150)
@@ -501,26 +501,54 @@
   // Prev / next chronological (kept for back-compat — "post-nav" feature
   // below the article still uses these as a simple linear navigation).
-  const prevPost = db.prepare(`
-    SELECT slug, title FROM posts
-    WHERE site_id = ? AND status = 'published' AND published_at < ? AND id != ?
-    ORDER BY published_at DESC LIMIT 1
-  `).get(site.id, post.published_at, post.id);
-
-  const nextPost = db.prepare(`
-    SELECT slug, title FROM posts
-    WHERE site_id = ? AND status = 'published' AND published_at > ? AND id != ?
-    ORDER BY published_at ASC LIMIT 1
-  `).get(site.id, post.published_at, post.id);
+  // Hub-modus: Gerelateerde posts + Newer/Older trekken uit ALLE users (alle
+  // sites), nieuwste->oudste. Solo-modus: binnen de huidige site (oud gedrag).
+  const isHub = res.locals.tenancy === 'hub';
+  // Per-post URL-basis: in hub wijst een link naar /user/<site-slug>/<post-slug>.
+  const urlBaseFor = (p) => (isHub && p && p.site_slug) ? `/user/${p.site_slug}` : '';
+
+  const prevPost = isHub
+    ? db.prepare(`
+        SELECT p.slug, p.title, s.slug AS site_slug
+        FROM posts p JOIN sites s ON s.id = p.site_id
+        WHERE p.status = 'published' AND p.published_at < ? AND p.id != ?
+        ORDER BY p.published_at DESC LIMIT 1
+      `).get(post.published_at, post.id)
+    : db.prepare(`
+        SELECT slug, title FROM posts
+        WHERE site_id = ? AND status = 'published' AND published_at < ? AND id != ?
+        ORDER BY published_at DESC LIMIT 1
+      `).get(site.id, post.published_at, post.id);
+
+  const nextPost = isHub
+    ? db.prepare(`
+        SELECT p.slug, p.title, s.slug AS site_slug
+        FROM posts p JOIN sites s ON s.id = p.site_id
+        WHERE p.status = 'published' AND p.published_at > ? AND p.id != ?
+        ORDER BY p.published_at ASC LIMIT 1
+      `).get(post.published_at, post.id)
+    : db.prepare(`
+        SELECT slug, title FROM posts
+        WHERE site_id = ? AND status = 'published' AND published_at > ? AND id != ?
+        ORDER BY published_at ASC LIMIT 1
+      `).get(site.id, post.published_at, post.id);
+  if (prevPost) prevPost._urlBase = urlBaseFor(prevPost);
+  if (nextPost) nextPost._urlBase = urlBaseFor(nextPost);
 
   // ── Related posts: same-tag matching with recency fallback ─────
   // Fetch ~50 candidates, score by tag overlap, take top 3.
   // Excluding self via `id != ?`.
-  const candidates = db.prepare(`
-    SELECT id, slug, title, cover_image_url, published_at, tags
-    FROM posts
-    WHERE site_id = ? AND status = 'published' AND id != ?
-    ORDER BY published_at DESC
-    LIMIT 50
-  `).all(site.id, post.id);
+  const candidates = isHub
+    ? db.prepare(`
+        SELECT p.id, p.slug, p.title, p.cover_image_url, p.published_at, p.tags, s.slug AS site_slug
+        FROM posts p JOIN sites s ON s.id = p.site_id
+        WHERE p.status = 'published' AND p.id != ?
+        ORDER BY p.published_at DESC LIMIT 50
+      `).all(post.id)
+    : db.prepare(`
+        SELECT id, slug, title, cover_image_url, published_at, tags
+        FROM posts
+        WHERE site_id = ? AND status = 'published' AND id != ?
+        ORDER BY published_at DESC LIMIT 50
+      `).all(site.id, post.id);
 
   // Parse tags JSON safely; missing/malformed → empty array.
@@ -558,5 +586,5 @@
   }
   // Strip the internal _overlap field before sending to view
-  relatedPosts = relatedPosts.map(({ _overlap, tags, ...rest }) => rest);
+  relatedPosts = relatedPosts.map(({ _overlap, tags, ...rest }) => ({ ...rest, _urlBase: urlBaseFor(rest) }));
 
   // ── Pinned navigation: prev/next pinned post ───────────────────
Index: src/views/pages/post.ejs
===================================================================
--- src/views/pages/post.ejs	(revision 7f46817d08f908b01fdf536e5e4935d81fb81c84)
+++ src/views/pages/post.ejs	(revision d54dadef21ca21f9a76468bde33d1e745e9f3150)
@@ -212,7 +212,7 @@
         <% relatedPosts.forEach(function(rp) { %>
           <li class="post-related-card">
-            <a href="/<%= rp.slug %>"
+            <a href="<%= rp._urlBase || '' %>/<%= rp.slug %>"<% if (!rp._urlBase) { %>
                hx-get="/<%= rp.slug %>?partial=1" hx-target="#pcms-main"
-               hx-push-url="/<%= rp.slug %>" hx-indicator="#pcms-loading">
+               hx-push-url="/<%= rp.slug %>" hx-indicator="#pcms-loading"<% } %>>
               <% if (rp.cover_image_url) { %>
                 <span class="post-related-cover"
@@ -242,7 +242,7 @@
           Uses prevPinnedPost which now means "smaller rank". %>
       <% if (prevPinnedPost) { %>
-        <a class="post-pinned-card post-pinned-card--prev" href="/<%= prevPinnedPost.slug %>"
-           hx-get="/<%= prevPinnedPost.slug %>?partial=1" hx-target="#pcms-main"
-           hx-push-url="/<%= prevPinnedPost.slug %>" hx-indicator="#pcms-loading">
+        <a class="post-pinned-card post-pinned-card--prev" href="<%= _base %>/<%= prevPinnedPost.slug %>"
+           hx-get="<%= _base %>/<%= prevPinnedPost.slug %>?partial=1" hx-target="#pcms-main"
+           hx-push-url="<%= _base %>/<%= prevPinnedPost.slug %>" hx-indicator="#pcms-loading">
           <span class="post-pinned-arrow" aria-hidden="true">←</span>
           <span class="post-pinned-body">
@@ -262,7 +262,7 @@
           Uses nextPinnedPost which now means "larger rank". %>
       <% if (nextPinnedPost) { %>
-        <a class="post-pinned-card post-pinned-card--next" href="/<%= nextPinnedPost.slug %>"
-           hx-get="/<%= nextPinnedPost.slug %>?partial=1" hx-target="#pcms-main"
-           hx-push-url="/<%= nextPinnedPost.slug %>" hx-indicator="#pcms-loading">
+        <a class="post-pinned-card post-pinned-card--next" href="<%= _base %>/<%= nextPinnedPost.slug %>"
+           hx-get="<%= _base %>/<%= nextPinnedPost.slug %>?partial=1" hx-target="#pcms-main"
+           hx-push-url="<%= _base %>/<%= nextPinnedPost.slug %>" hx-indicator="#pcms-loading">
           <span class="post-pinned-arrow" aria-hidden="true">→</span>
           <span class="post-pinned-body">
@@ -287,7 +287,7 @@
     <nav class="post-nav">
       <% if (prevPost) { %>
-        <a class="post-nav-prev" href="/<%= prevPost.slug %>"
+        <a class="post-nav-prev" href="<%= prevPost._urlBase || '' %>/<%= prevPost.slug %>"<% if (!prevPost._urlBase) { %>
            hx-get="/<%= prevPost.slug %>?partial=1" hx-target="#pcms-main"
-           hx-push-url="/<%= prevPost.slug %>" hx-indicator="#pcms-loading">
+           hx-push-url="/<%= prevPost.slug %>" hx-indicator="#pcms-loading"<% } %>>
           <span class="post-nav-label">← Older</span>
           <span class="post-nav-title"><%= prevPost.title %></span>
@@ -295,7 +295,7 @@
       <% } else { %><span></span><% } %>
       <% if (nextPost) { %>
-        <a class="post-nav-next" href="/<%= nextPost.slug %>"
+        <a class="post-nav-next" href="<%= nextPost._urlBase || '' %>/<%= nextPost.slug %>"<% if (!nextPost._urlBase) { %>
            hx-get="/<%= nextPost.slug %>?partial=1" hx-target="#pcms-main"
-           hx-push-url="/<%= nextPost.slug %>" hx-indicator="#pcms-loading">
+           hx-push-url="/<%= nextPost.slug %>" hx-indicator="#pcms-loading"<% } %>>
           <span class="post-nav-label">Newer →</span>
           <span class="post-nav-title"><%= nextPost.title %></span>
