Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision d41f4be141f22853757243f454d4b432b894d91a)
+++ src/config/database.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -252,4 +252,22 @@
   `);
 
+  // Meldingen: iemand reageert op je reactie / post, of liket je post. Snapshots
+  // van naam/titel zodat de lijst goedkoop te tonen is zonder joins.
+  db.exec(`
+    CREATE TABLE IF NOT EXISTS notifications (
+      id TEXT PRIMARY KEY,
+      user_id TEXT NOT NULL,
+      type TEXT NOT NULL,
+      actor_id TEXT,
+      actor_name TEXT,
+      post_slug TEXT,
+      post_title TEXT,
+      url TEXT,
+      read INTEGER DEFAULT 0,
+      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+    );
+    CREATE INDEX IF NOT EXISTS idx_notif_user ON notifications(user_id, read, created_at);
+  `);
+
   // Link-in-bio klikstatistiek (premium #6). Per (site, url) een teller; de
   // link-in-bio-pagina linkt via /links/go/:i dat de klik telt en doorstuurt.
Index: src/middleware/render.js
===================================================================
--- src/middleware/render.js	(revision d41f4be141f22853757243f454d4b432b894d91a)
+++ src/middleware/render.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -19,4 +19,5 @@
 import { getSetting } from '../services/SettingsService.js';
 import { isPremium as isPremiumInstance, premiumEnabled, premiumUnlocked } from '../services/PatreonService.js';
+import { unreadCount as notifUnreadCount } from '../services/NotificationService.js';
 import { PLATFORMS as PLATFORMS_CATALOG } from '../services/PlatformIcons.js';
 import { t as i18nT, resolveLang, SUPPORTED as LANGS, LANG_NAMES } from '../services/i18n.js';
@@ -94,4 +95,5 @@
     langs: LANGS.map((c) => ({ code: c, name: LANG_NAMES[c], active: c === _lang })),
     timezone: getSetting('timezone') || '',
+    notifUnread: _u ? notifUnreadCount(_u.id) : 0,
     userOwnsSite,
     canSeeBeheer,
Index: src/routes/comments.js
===================================================================
--- src/routes/comments.js	(revision d41f4be141f22853757243f454d4b432b894d91a)
+++ src/routes/comments.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -19,4 +19,5 @@
 import { requireAuth } from '../middleware/auth.js';
 import PermissionsService from '../services/PermissionsService.js';
+import { notify } from '../services/NotificationService.js';
 
 const router = express.Router();
@@ -39,5 +40,5 @@
 
   const post = db.prepare(
-    'SELECT id, slug FROM posts WHERE site_id = ? AND slug = ? AND status = ?'
+    'SELECT id, slug, title, author_id FROM posts WHERE site_id = ? AND slug = ? AND status = ?'
   ).get(site.id, postSlug, 'published');
   if (!post) return res.status(404).send('Post not found');
@@ -50,10 +51,12 @@
   // up to the top-level parent so we never go deeper than 1)
   let resolvedParent = null;
+  let parentAuthorId = null;
   if (parentId) {
     const parent = db.prepare(
-      'SELECT id, parent_comment_id FROM comments WHERE id = ? AND post_id = ?'
+      'SELECT id, parent_comment_id, author_id FROM comments WHERE id = ? AND post_id = ?'
     ).get(parentId, post.id);
     if (!parent) return res.status(400).send('Invalid parent comment');
     resolvedParent = parent.parent_comment_id || parent.id;
+    parentAuthorId = parent.author_id; // ontvanger van de "antwoord"-melding
   }
 
@@ -73,4 +76,18 @@
     VALUES (?, ?, ?, ?, ?, ?)
   `).run(commentId, post.id, req.session.user.id, resolvedParent, rawContent, status);
+
+  // Melding (alleen bij een zichtbare reactie): antwoord → de auteur van de reactie
+  // waarop gereageerd is; top-level reactie → de auteur van de post. notify() slaat
+  // jezelf-notificeren over.
+  if (status === 'approved') {
+    const url = `${res.locals.siteUrlBase || ''}/${post.slug}#comment-${commentId}`;
+    const actorId = req.session.user.id;
+    const actorName = req.session.user.username;
+    if (parentId) {
+      notify({ userId: parentAuthorId, actorId, actorName, type: 'reply', postSlug: post.slug, postTitle: post.title, url });
+    } else {
+      notify({ userId: post.author_id, actorId, actorName, type: 'comment', postSlug: post.slug, postTitle: post.title, url });
+    }
+  }
 
   // Where to land after submit:
Index: src/routes/notifications.js
===================================================================
--- src/routes/notifications.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
+++ src/routes/notifications.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -0,0 +1,23 @@
+/**
+ * GET /notifications — meldingenpagina voor de ingelogde gebruiker.
+ * Openen = alles als gelezen markeren (de teller in de header valt dan weg).
+ */
+import express from 'express';
+import { requireAuth } from '../middleware/auth.js';
+import { renderPage } from '../middleware/render.js';
+import { list, markAllRead } from '../services/NotificationService.js';
+
+const router = express.Router();
+
+router.get('/', requireAuth, (req, res) => {
+  const uid = req.session.user.id;
+  const items = list(uid, 50);
+  markAllRead(uid);
+  renderPage(req, res, 'pages/notifications', {
+    pageTitle: 'Meldingen',
+    bodyClass: 'on-special',
+    items,
+  });
+});
+
+export default router;
Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision d41f4be141f22853757243f454d4b432b894d91a)
+++ src/routes/posts.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -10,4 +10,5 @@
 import { renderPage } from '../middleware/render.js';
 import { recordPageview, recordPostView } from '../services/StatsService.js';
+import { notify } from '../services/NotificationService.js';
 import PermissionsService from '../services/PermissionsService.js';
 import MarkdownService from '../services/MarkdownService.js';
@@ -405,5 +406,5 @@
 router.post('/posts/:id/like', requireAuth, (req, res) => {
   const userId = req.session.user.id;
-  const post = db.prepare('SELECT id, status FROM posts WHERE id = ?').get(req.params.id);
+  const post = db.prepare('SELECT id, status, slug, title, author_id FROM posts WHERE id = ?').get(req.params.id);
   if (!post) return res.status(404).send('Post niet gevonden');
   if (post.status !== 'published') return res.status(403).send('Niet beschikbaar');
@@ -414,4 +415,9 @@
   } else {
     db.prepare('INSERT OR IGNORE INTO post_likes (post_id, user_id) VALUES (?, ?)').run(post.id, userId);
+    // Melding voor de post-auteur (notify slaat jezelf-liken over).
+    notify({
+      userId: post.author_id, actorId: userId, actorName: req.session.user.username, type: 'like',
+      postSlug: post.slug, postTitle: post.title, url: (res.locals.siteUrlBase || '') + '/' + post.slug,
+    });
   }
   const likeCount = db.prepare('SELECT COUNT(*) AS c FROM post_likes WHERE post_id = ?').get(post.id).c;
Index: src/server.js
===================================================================
--- src/server.js	(revision d41f4be141f22853757243f454d4b432b894d91a)
+++ src/server.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -27,4 +27,5 @@
 import authRoutes from './routes/auth.js';
 import accountRoutes from './routes/account.js';
+import notificationsRoutes from './routes/notifications.js';
 import adminRoutes from './routes/admin.js';
 import adminAudioRoutes from './routes/admin-audio.js';
@@ -257,4 +258,5 @@
 app.use('/auth', authRoutes);
 app.use('/account', accountRoutes);
+app.use('/notifications', notificationsRoutes);
 app.use('/admin/audio', adminAudioRoutes);
 app.use('/admin/playlists', adminPlaylistsRoutes);
Index: src/services/NotificationService.js
===================================================================
--- src/services/NotificationService.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
+++ src/services/NotificationService.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -0,0 +1,38 @@
+/**
+ * Meldingen — antwoord op je reactie, reactie op je post, like op je post.
+ * Voor élke ingelogde gebruiker (Google-bezoekers/fans én admin). Snapshots van
+ * actor-naam + post-titel zodat de lijst zonder joins te tonen is.
+ */
+import { randomUUID } from 'crypto';
+import db from '../config/database.js';
+
+// Maakt een melding aan. Doet niets als er geen ontvanger is of als je jezelf
+// zou notificeren (eigen reactie/like op eigen post/reactie).
+export function notify({ userId, actorId, actorName, type, postSlug, postTitle, url }) {
+  if (!userId || userId === actorId) return;
+  try {
+    db.prepare(`
+      INSERT INTO notifications (id, user_id, type, actor_id, actor_name, post_slug, post_title, url, read)
+      VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)
+    `).run(randomUUID(), userId, type, actorId || null, actorName || null, postSlug || null, postTitle || null, url || null);
+  } catch { /* meldingen zijn niet-fataal */ }
+}
+
+export function unreadCount(userId) {
+  if (!userId) return 0;
+  try { return db.prepare('SELECT COUNT(*) AS c FROM notifications WHERE user_id = ? AND read = 0').get(userId).c; }
+  catch { return 0; }
+}
+
+export function list(userId, limit = 50) {
+  if (!userId) return [];
+  try { return db.prepare('SELECT * FROM notifications WHERE user_id = ? ORDER BY created_at DESC LIMIT ?').all(userId, limit); }
+  catch { return []; }
+}
+
+export function markAllRead(userId) {
+  if (!userId) return;
+  try { db.prepare('UPDATE notifications SET read = 1 WHERE user_id = ? AND read = 0').run(userId); } catch { /* noop */ }
+}
+
+export default { notify, unreadCount, list, markAllRead };
Index: src/services/i18n.js
===================================================================
--- src/services/i18n.js	(revision d41f4be141f22853757243f454d4b432b894d91a)
+++ src/services/i18n.js	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -25,4 +25,7 @@
     'nav.new_post': 'Nieuwe post',
     'nav.language': 'Taal',
+    'nav.notifications': 'Meldingen',
+    'notif.title': 'Meldingen', 'notif.empty': 'Nog geen meldingen.', 'notif.someone': 'Iemand',
+    'notif.reply': '{actor} reageerde op je reactie', 'notif.comment': '{actor} reageerde op je post', 'notif.like': '{actor} vindt je post leuk',
     'switch.agenda': 'Agenda',
     'switch.solo': 'Solo',
@@ -999,4 +1002,7 @@
     'nav.new_post': 'New post',
     'nav.language': 'Language',
+    'nav.notifications': 'Notifications',
+    'notif.title': 'Notifications', 'notif.empty': 'No notifications yet.', 'notif.someone': 'Someone',
+    'notif.reply': '{actor} replied to your comment', 'notif.comment': '{actor} commented on your post', 'notif.like': '{actor} liked your post',
     'switch.agenda': 'Agenda',
     'switch.solo': 'Solo',
@@ -1964,4 +1970,7 @@
     'nav.new_post': 'Neuer Beitrag',
     'nav.language': 'Sprache',
+    'nav.notifications': 'Benachrichtigungen',
+    'notif.title': 'Benachrichtigungen', 'notif.empty': 'Noch keine Benachrichtigungen.', 'notif.someone': 'Jemand',
+    'notif.reply': '{actor} hat auf deinen Kommentar geantwortet', 'notif.comment': '{actor} hat deinen Beitrag kommentiert', 'notif.like': '{actor} gefällt dein Beitrag',
     'switch.agenda': 'Termine',
     'switch.solo': 'Solo',
Index: src/views/pages/notifications.ejs
===================================================================
--- src/views/pages/notifications.ejs	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
+++ src/views/pages/notifications.ejs	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -0,0 +1,36 @@
+<div class="container notif-page">
+  <h1><%= t('notif.title') %></h1>
+
+  <% if (!items || !items.length) { %>
+    <p class="notif-empty"><%= t('notif.empty') %></p>
+  <% } else { %>
+    <ul class="notif-list">
+      <% items.forEach(function(n){
+           var key = n.type === 'reply' ? 'notif.reply' : (n.type === 'comment' ? 'notif.comment' : 'notif.like');
+           var actor = n.actor_name || t('notif.someone');
+      %>
+        <li class="notif-item<%= n.read ? '' : ' is-unread' %>">
+          <a class="notif-link" href="<%= n.url || '#' %>">
+            <span class="notif-text"><%= t(key, { actor: actor }) %></span>
+            <% if (n.post_title) { %><span class="notif-post">&ldquo;<%= n.post_title %>&rdquo;</span><% } %>
+            <span class="notif-time"><%= formatDateTime(n.created_at) %></span>
+          </a>
+        </li>
+      <% }); %>
+    </ul>
+  <% } %>
+</div>
+
+<style>
+.notif-page { max-width: 640px; margin: 2.5rem auto; padding: 0 1rem; }
+.notif-page h1 { font-family: var(--font-display, serif); font-size: 1.8rem; margin: 0 0 1.25rem; }
+.notif-empty { color: var(--ink-muted, var(--ink-soft)); }
+.notif-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
+.notif-item { border: 1px solid var(--rule); border-radius: 10px; background: var(--paper-2); }
+.notif-item.is-unread { border-color: var(--accent); }
+.notif-link { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.7rem 0.9rem; color: var(--ink); text-decoration: none; }
+.notif-link:hover { background: var(--paper); }
+.notif-text { font-size: 0.95rem; }
+.notif-post { color: var(--ink-soft, var(--ink-muted)); font-size: 0.9rem; }
+.notif-time { color: var(--ink-soft, var(--ink-muted)); font-size: 0.78rem; }
+</style>
Index: src/views/partials/bottom-tab.ejs
===================================================================
--- src/views/partials/bottom-tab.ejs	(revision d41f4be141f22853757243f454d4b432b894d91a)
+++ src/views/partials/bottom-tab.ejs	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -107,4 +107,7 @@
       <% } %>
       <span class="bottom-tab-label"><%= t('tab.profile') %></span>
+      <% if (typeof notifUnread !== 'undefined' && notifUnread > 0) { %>
+        <span class="bottom-tab-badge" aria-label="<%= notifUnread %> ongelezen meldingen"><%= notifUnread > 99 ? '99+' : notifUnread %></span>
+      <% } %>
     </button>
   <% } else { %>
Index: src/views/partials/profile-sheet.ejs
===================================================================
--- src/views/partials/profile-sheet.ejs	(revision d41f4be141f22853757243f454d4b432b894d91a)
+++ src/views/partials/profile-sheet.ejs	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -53,4 +53,11 @@
           <svg class="psi-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
           <span class="psi-label">Account</span>
+          <svg class="psi-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"/></svg>
+        </a>
+      </li>
+      <li role="none">
+        <a href="/notifications" class="profile-sheet-item" role="menuitem" data-close-sheet>
+          <svg class="psi-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
+          <span class="psi-label"><%= t('nav.notifications') %><% if (typeof notifUnread !== 'undefined' && notifUnread > 0) { %> (<%= notifUnread %>)<% } %></span>
           <svg class="psi-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"/></svg>
         </a>
Index: src/views/partials/topnav.ejs
===================================================================
--- src/views/partials/topnav.ejs	(revision d41f4be141f22853757243f454d4b432b894d91a)
+++ src/views/partials/topnav.ejs	(revision c9c6a2d6cf1cf1611a79c699dd70c435bda3c203)
@@ -103,4 +103,12 @@
         <a class="nav-btn" href="<%= _siteUrlBase %>/prutter" aria-label="Prutter (DMs)" title="Prutter">
           <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
+        </a>
+      <% } %>
+
+      <% if (user) { %>
+        <a class="nav-btn nav-notif" href="/notifications" aria-label="<%= t('nav.notifications') %>" title="<%= t('nav.notifications') %>"
+           hx-get="/notifications?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" hx-push-url="/notifications" hx-indicator="#pcms-loading">
+          <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
+          <% if (typeof notifUnread !== 'undefined' && notifUnread > 0) { %><span class="notif-badge"><%= notifUnread > 9 ? '9+' : notifUnread %></span><% } %>
         </a>
       <% } %>
@@ -137,4 +145,10 @@
                 <svg class="udi-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
                 <span><%= t('nav.account') %></span>
+              </a>
+              <a href="/notifications" role="menuitem" class="udi"
+                 hx-get="/notifications?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
+                 hx-push-url="/notifications" hx-indicator="#pcms-loading">
+                <svg class="udi-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
+                <span><%= t('nav.notifications') %><% if (typeof notifUnread !== 'undefined' && notifUnread > 0) { %> (<%= notifUnread %>)<% } %></span>
               </a>
               <a href="/favorieten" role="menuitem" class="udi"
@@ -558,3 +572,12 @@
 .ss-all { display: block; margin-top: .35rem; padding: .55rem .6rem; border-top: 1px solid var(--rule); color: var(--accent); font-weight: 600; text-decoration: none; font-size: .9rem; }
 .ss-all:hover { text-decoration: underline; }
+/* Meldingen-bel + ongelezen-badge */
+.nav-notif { position: relative; }
+.notif-badge {
+  position: absolute; top: 1px; right: 1px;
+  min-width: 16px; height: 16px; padding: 0 4px; box-sizing: border-box;
+  display: inline-flex; align-items: center; justify-content: center;
+  background: #e0245e; color: #fff; font-size: 10px; font-weight: 700;
+  border-radius: 999px; line-height: 1; pointer-events: none;
+}
 </style>
