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:
163
frontend/src/app/llms.txt/route.ts
Normal file
163
frontend/src/app/llms.txt/route.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://spanglish.com.py';
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
|
||||
interface LlmsEvent {
|
||||
id: string;
|
||||
title: string;
|
||||
titleEs?: string;
|
||||
shortDescription?: string;
|
||||
shortDescriptionEs?: string;
|
||||
description: string;
|
||||
descriptionEs?: string;
|
||||
startDatetime: string;
|
||||
endDatetime?: string;
|
||||
location: string;
|
||||
price: number;
|
||||
currency: string;
|
||||
availableSeats?: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
async function getNextUpcomingEvent(): Promise<LlmsEvent | null> {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/events/next/upcoming`, {
|
||||
next: { tags: ['next-event'] },
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const data = await response.json();
|
||||
return data.event || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getUpcomingEvents(): Promise<LlmsEvent[]> {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/events?status=published&upcoming=true`, {
|
||||
next: { tags: ['next-event'] },
|
||||
});
|
||||
if (!response.ok) return [];
|
||||
const data = await response.json();
|
||||
return data.events || [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function formatEventDate(dateStr: string): string {
|
||||
return new Date(dateStr).toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
function formatEventTime(dateStr: string): string {
|
||||
return new Date(dateStr).toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true,
|
||||
});
|
||||
}
|
||||
|
||||
function formatPrice(price: number, currency: string): string {
|
||||
if (price === 0) return 'Free';
|
||||
return `${price.toLocaleString()} ${currency}`;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const [nextEvent, upcomingEvents] = await Promise.all([
|
||||
getNextUpcomingEvent(),
|
||||
getUpcomingEvents(),
|
||||
]);
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
// Header
|
||||
lines.push('# Spanglish Community');
|
||||
lines.push('');
|
||||
lines.push('> English-Spanish language exchange community organizing social events and meetups in Asunción, Paraguay.');
|
||||
lines.push('');
|
||||
lines.push(`- Website: ${siteUrl}`);
|
||||
lines.push(`- Events page: ${siteUrl}/events`);
|
||||
|
||||
// Social links
|
||||
const instagram = process.env.NEXT_PUBLIC_INSTAGRAM;
|
||||
const whatsapp = process.env.NEXT_PUBLIC_WHATSAPP;
|
||||
const telegram = process.env.NEXT_PUBLIC_TELEGRAM;
|
||||
const email = process.env.NEXT_PUBLIC_EMAIL;
|
||||
|
||||
if (instagram) lines.push(`- Instagram: https://instagram.com/${instagram}`);
|
||||
if (telegram) lines.push(`- Telegram: https://t.me/${telegram}`);
|
||||
if (email) lines.push(`- Email: ${email}`);
|
||||
if (whatsapp) lines.push(`- WhatsApp: ${whatsapp}`);
|
||||
|
||||
lines.push('');
|
||||
|
||||
// Next Event (most important section for AI)
|
||||
lines.push('## Next Event');
|
||||
lines.push('');
|
||||
|
||||
if (nextEvent) {
|
||||
lines.push(`- Event: ${nextEvent.title}`);
|
||||
lines.push(`- Date: ${formatEventDate(nextEvent.startDatetime)}`);
|
||||
lines.push(`- Time: ${formatEventTime(nextEvent.startDatetime)}`);
|
||||
if (nextEvent.endDatetime) {
|
||||
lines.push(`- End time: ${formatEventTime(nextEvent.endDatetime)}`);
|
||||
}
|
||||
lines.push(`- Location: ${nextEvent.location}, Asunción, Paraguay`);
|
||||
lines.push(`- Price: ${formatPrice(nextEvent.price, nextEvent.currency)}`);
|
||||
if (nextEvent.availableSeats !== undefined) {
|
||||
lines.push(`- Available spots: ${nextEvent.availableSeats}`);
|
||||
}
|
||||
lines.push(`- Details and tickets: ${siteUrl}/events/${nextEvent.id}`);
|
||||
if (nextEvent.shortDescription) {
|
||||
lines.push(`- Description: ${nextEvent.shortDescription}`);
|
||||
}
|
||||
} else {
|
||||
lines.push('No upcoming events currently scheduled. Check back soon or follow us on social media for announcements.');
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
|
||||
// All upcoming events
|
||||
if (upcomingEvents.length > 1) {
|
||||
lines.push('## All Upcoming Events');
|
||||
lines.push('');
|
||||
for (const event of upcomingEvents) {
|
||||
lines.push(`### ${event.title}`);
|
||||
lines.push(`- Date: ${formatEventDate(event.startDatetime)}`);
|
||||
lines.push(`- Time: ${formatEventTime(event.startDatetime)}`);
|
||||
lines.push(`- Location: ${event.location}, Asunción, Paraguay`);
|
||||
lines.push(`- Price: ${formatPrice(event.price, event.currency)}`);
|
||||
lines.push(`- Details: ${siteUrl}/events/${event.id}`);
|
||||
lines.push('');
|
||||
}
|
||||
}
|
||||
|
||||
// About section
|
||||
lines.push('## About Spanglish');
|
||||
lines.push('');
|
||||
lines.push('Spanglish is a language exchange community based in Asunción, Paraguay. We organize regular social events where people can practice English and Spanish in a relaxed, friendly environment. Our events bring together locals and internationals for conversation, cultural exchange, and fun.');
|
||||
lines.push('');
|
||||
lines.push('## Frequently Asked Questions');
|
||||
lines.push('');
|
||||
lines.push('- **What is Spanglish?** A language exchange community that hosts social events to practice English and Spanish.');
|
||||
lines.push('- **Where are events held?** In Asunción, Paraguay. Specific venues are listed on each event page.');
|
||||
lines.push('- **How do I attend an event?** Visit the events page to see upcoming events and book tickets.');
|
||||
lines.push('- **How much do events cost?** Prices vary by event. Some are free, others have a small cover charge.');
|
||||
lines.push(`- **How do I stay updated?** Follow us on Instagram${instagram ? ` (@${instagram})` : ''}, join our Telegram${telegram ? ` (@${telegram})` : ''}, or check ${siteUrl}/events regularly.`);
|
||||
lines.push('');
|
||||
|
||||
const content = lines.join('\n');
|
||||
|
||||
return new NextResponse(content, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'Cache-Control': 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400',
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user