Make next event visible to AI crawlers (SSR, JSON-LD, meta, llms.txt)

- SSR next event on homepage; pass initialEvent from server to avoid client-only content
- Add schema.org Event JSON-LD on homepage when next event exists
- Dynamic homepage metadata (description, OG, Twitter) with next event date
- Add dynamic /llms.txt route for AI-friendly plain-text event info
- Revalidation: support next-event tag; backend revalidates sitemap + next-event on event CUD
- Allow /llms.txt in robots.txt

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Michilis
2026-02-12 04:10:49 +00:00
parent af94c99fd2
commit 5885044369
7 changed files with 348 additions and 23 deletions

View File

@@ -15,25 +15,26 @@ interface UserContext {
const eventsRouter = new Hono<{ Variables: { user: UserContext } }>();
// Trigger frontend sitemap revalidation (fire-and-forget)
function revalidateSitemap() {
// Trigger frontend cache revalidation (fire-and-forget)
// Revalidates both the sitemap and the next-event data (homepage, llms.txt)
function revalidateFrontendCache() {
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3002';
const secret = process.env.REVALIDATE_SECRET;
if (!secret) {
console.warn('REVALIDATE_SECRET not set, skipping sitemap revalidation');
console.warn('REVALIDATE_SECRET not set, skipping frontend revalidation');
return;
}
fetch(`${frontendUrl}/api/revalidate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ secret, tag: 'events-sitemap' }),
body: JSON.stringify({ secret, tag: ['events-sitemap', 'next-event'] }),
})
.then((res) => {
if (!res.ok) console.error('Sitemap revalidation failed:', res.status);
else console.log('Sitemap revalidation triggered');
if (!res.ok) console.error('Frontend revalidation failed:', res.status);
else console.log('Frontend revalidation triggered (sitemap + next-event)');
})
.catch((err) => {
console.error('Sitemap revalidation error:', err.message);
console.error('Frontend revalidation error:', err.message);
});
}
@@ -360,7 +361,7 @@ eventsRouter.post('/', requireAuth(['admin', 'organizer']), zValidator('json', c
await (db as any).insert(events).values(newEvent);
// Revalidate sitemap when a new event is created
revalidateSitemap();
revalidateFrontendCache();
// Return normalized event data
return c.json({ event: normalizeEvent(newEvent) }, 201);
@@ -399,7 +400,7 @@ eventsRouter.put('/:id', requireAuth(['admin', 'organizer']), zValidator('json',
);
// Revalidate sitemap when an event is updated (status/dates may have changed)
revalidateSitemap();
revalidateFrontendCache();
return c.json({ event: normalizeEvent(updated) });
});
@@ -458,7 +459,7 @@ eventsRouter.delete('/:id', requireAuth(['admin']), async (c) => {
await (db as any).delete(events).where(eq((events as any).id, id));
// Revalidate sitemap when an event is deleted
revalidateSitemap();
revalidateFrontendCache();
return c.json({ message: 'Event deleted successfully' });
});