import type { Metadata } from 'next'; import Link from 'next/link'; import { LaunchFilterForm, launchHref, launchTitle, parseLaunchQuery } from '@/components/launches/launch-filters'; import { LaunchTimelineCharts } from '@/components/launches/launch-timeline'; import { LaunchesTable } from '@/components/launches/launches-table'; import { Pagination } from '@/components/ui/pagination'; import { Container, PageHeader } from '@/components/ui/section'; import { Unavailable } from '@/components/ui/unavailable'; import { api, safe } from '@/lib/api'; import { fmtInt } from '@/lib/format'; import { routes, SITE_URL } from '@/lib/site'; const PAGE_SIZE = 50; type Props = { searchParams: Promise> }; export async function generateMetadata({ searchParams }: Props): Promise { const q = parseLaunchQuery(await searchParams); const filtered = Object.keys(q).some((k) => k !== 'page' && k !== 'sort'); // Same cached request as the page body → real site names in the title instead of slugs. const sites = q.site ? ((await safe(api.launchTimeline()))?.data.sites ?? null) : null; const title = filtered ? `${launchTitle(q, sites)} — Launches` : 'Launches — every orbital launch since 1957, derived from the catalogue'; const description = 'Orbital launches reconstructed from SATCAT international designators: date, site, payload count, catalogued objects still on orbit, owners. Filter by year, site, country and owner.'; const canonical = launchHref(q, {}, false); return { title, description, alternates: { canonical }, robots: q.page || q.q ? { index: false, follow: true } : undefined, openGraph: { title, description, url: `${SITE_URL}${canonical}` }, twitter: { card: 'summary_large_image', title, description } }; } export default async function LaunchesPage({ searchParams }: Props) { const q = parseLaunchQuery(await searchParams); const page = Math.max(1, Number(q.page ?? '1') || 1); const [list, timelineRes] = await Promise.all([safe(api.launches({ ...q, page, page_size: PAGE_SIZE, sort: q.sort ?? 'date' })), safe(api.launchTimeline())]); const timeline = timelineRes?.data ?? null; const filtered = Object.keys(q).some((k) => k !== 'page' && k !== 'sort'); const total = list?.pagination.total ?? null; return ( Launch record · {total !== null ? {fmtInt(total)} launches{filtered ? ' match' : ''} : 'count unavailable'}} title={filtered ? launchTitle(q, timeline?.sites ?? null) : 'Orbital launches'} lede={filtered ? undefined : 'Every launch that left at least one catalogued object in orbit, grouped by international designator. Counts of payloads, rocket bodies and debris are taken from the live catalogue, so "on orbit" changes as objects decay.'} />
{timeline ? : }
{filtered && (
    {(Object.keys(q) as (keyof typeof q)[]).filter((k) => k !== 'page' && k !== 'sort').map((k) => (
  • {k.replace(/_/g, ' ')}: {q[k]} ×
  • ))}
)}
{list === null ? ( ) : list.data.length === 0 ? (

No launch matches these filters.

Clear filters
) : ( <> launchHref(q, { page: String(p) }, false)} className="mb-3" /> launchHref(q, { owner: c })} /> launchHref(q, { page: String(p) }, false)} className="mt-4" /> )}

Launches are derived: SatelliteIndex groups catalogued objects by the launch part of their COSPAR international designator (e.g. 1998-067) and takes the earliest launch date and the launch site from SATCAT. Launches that placed nothing in the catalogue (failures below orbit, suborbital flights) do not appear. Region split uses the launch-site country. See the methodology.

); }