GMT+8 SG --:-- 0000 X 0000 Y /
← 全部文章← All writing

VOLO engineering diary

Engineering Diary, Day 17: 211,153 Flights, 200+ Entity Links, and the SEO Web We Wove Through Our Insights Hub

Originally published on flyvolo.ai as part of the VOLO Engineering Diary (2026-03-09), documenting the build of the VOLO private-aviation platform between February and May 2026. When VOLO changed direction in September 2026 the 25 entries were moved here as an archive. The text is unchanged; the products, pages and links it mentions no longer exist. Original byline: VOLO CTO.

Two Jobs, One Theme: Connection

Today had two distinct halves. The morning was about creating data — a 720-line TypeScript file encoding every metric from the February 2026 Global Business Aviation Report. The afternoon was about connecting that data — weaving links between every entity mention and its dedicated detail page, turning our Insights Hub into an interconnected knowledge graph.

Both halves serve the same goal: making our 200+ entity pages discoverable — by users clicking through, by Google crawling link paths, and by AI systems following structured data.

Part 1: February 2026 Report

The raw source was a 104-page HTML report from Avi-Go covering every business aviation departure worldwide. We distilled it into structured TypeScript data: market overview, regional breakdowns, 13-month rolling trends, airport rankings (30 airports across 3 regions), aircraft utilization for 15 models, operator market share, cross-border route corridors, flight range distribution, and narrative analysis for every section.

The Numbers

MetricFebruary 2026Change
Total departures211,153-16.51% YoY
Daily average7,541-19.89% MoM
Active fleet16,958
North America share77.35%163,330 flights
Europe share13.35%28,197 flights
Rest of World9.29%19,626 flights

The most interesting story: the Bombardier Challenger 300 Series dethroned the Embraer Phenom 300 as the world's most-flown business jet. The Phenom 300 dropped -38.38% month-over-month while the Challenger 300 only fell -15.77%. The divergence suggests charter-heavy fleets (Phenom 300) are far more seasonal than fractional/corporate fleets (Challenger 300).

The busiest individual aircraft? SE-RVF, a Pilatus PC-24 operated by Swedish Air Ambulance — 128 flights in 28 days, or 4.6 flights per day. Not a billionaire's toy, but essential medical transport infrastructure.

Technical Implementation

The report file (february-2026.ts) follows the same ReportData type used by our 5 previous monthly reports. After creating the data, we updated the report registry (reports.ts) with SEO metadata, Chinese name mappings, and the report period. One commit, one next build, done.

Part 2: The Entity Linking Project

This is where the real SEO engineering happened. Our Insights Hub has 200+ entity detail pages — individual pages for airports like KTEB, aircraft like the Citation Latitude, operators like NetJets, routes like Miami-New York, and countries. These pages had been live for weeks, but they were orphans: nothing linked to them except the directory listing pages.

Every time a monthly report mentioned "Teterboro" or "Challenger 300 Series" or "NetJets Aviation," it was just plain text. No link. No way for Google to discover the entity page. No way for a user to click through and explore.

The Pattern

The solution was systematic: for every entity type (airports, aircraft, operators, routes, countries), we compute a Set of valid slugs at render time, then conditionally wrap entity names in <Link> components only when the entity has a dedicated page.

// Compute valid entity sets (server component, runs at build time)
const validAirports = new Set(extractAllAirports().map(a => a.slug));
const validAircraft = new Set(extractAllAircraft().map(a => a.slug));
const validOperators = new Set(extractAllOperators().map(o => o.slug));
 
// In render: conditional linking
{validAirports.has(airportSlug(r.icao)) ? (
  <Link href={`/insights/airports/${airportSlug(r.icao)}`}>
    {r.city}
  </Link>
) : (
  <span>{r.city}</span>
)}

This pattern ensures we never create broken links — if an entity appears in a report but doesn't meet our minimum-appearances threshold (2+ months of data), it stays as plain text.

11 Files, 408 Lines

We applied this pattern across:

  • Monthly report page ([slug]/page.tsx) — 8 render functions updated: country rankings, airport rankings, aircraft utilization, flights-by-model (2 tables), city pair routes, cross-border routes, operator rankings, plus 3 spotlight sections (featured route, featured aircraft, most popular model)
  • 5 topic pages — top-airports, top-operators, top-routes, aircraft-utilization, aircraft-types — each got conditional entity links in their RankingTable columns and monthly comparison tables
  • 5 directory pages — airports, aircraft, operators, routes, countries — each got ItemListJsonLd structured data

Cross-Border Route Parsing

The trickiest part was cross-border routes. Report data stores these as strings like "Teterboro → Toronto" using the Unicode arrow character (U+2192). To generate a linkable slug, we split on the arrow and pass the two city names to routeSlug(from, to):

const parts = r.name.split(/\s*\u2192\s*/);
const rs = parts.length >= 2 ? routeSlug(parts[0], parts[1]) : "";
return validRoutes.has(rs) ? (
  <Link href={`/insights/routes/${rs}`}>{r.name}</Link>
) : (
  <span>{r.name}</span>
);

Part 3: Structured Data for Discovery

Internal links help Google discover entity pages when it crawls a report. But we also wanted to give search engines explicit signals about the directory structure. We added ItemListJsonLd to all 5 entity directory pages, generating Schema.org ItemList markup with every entity as a ListItem:

<ItemListJsonLd
  items={allAirports.map((apt, i) => ({
    name: `${apt.city} (${apt.icao})`,
    url: `${baseUrl}/insights/airports/${apt.slug}`,
    position: i + 1,
  }))}
/>

This gives Google a machine-readable inventory of every entity we track — essentially saying "here are 60+ airport pages, ranked in this order, at these URLs."

Part 4: IndexNow

After deploying, we triggered our IndexNow cron job to submit all updated URLs for reindexing. The API accepted 1,034 URLs — every insight page, report page, entity page, and directory page across all 4 locales.

Why This Matters for SEO/GEO

Before today, our Insights Hub was a collection of well-designed but disconnected pages. A user landing on the February 2026 report could read about Teterboro's 7,821 flights but had no way to click through to Teterboro's full profile page. Google could find entity pages through the directory listings, but the monthly reports — our highest-value content — were dead ends.

After today:

  • Link equity flows from high-value report pages to entity detail pages
  • Crawl paths now connect reports → entities → directories in a dense web
  • User journey depth increases — readers can explore from "Challenger 300 had 16,700 flights" to the Challenger 300's full profile page
  • Structured data (ItemList + BreadcrumbJsonLd) gives search engines explicit entity relationships
  • GEO signals — AI systems can now follow entity links to find authoritative, granular data about specific airports, aircraft, and operators

The Commit Log

Two commits today:

  • c5961cf — insights: add February 2026 Global Business Aviation Report
  • e915d5c — seo: add internal entity linking + ItemList JSON-LD to insights pages (11 files, +408/-85 lines)

What's Next

  • Expand entity linking to EU/ROW city pair routes (currently only NA routes have entity pages)
  • Add cross-entity-type links on entity detail pages (e.g., airport page links to top operators at that airport)
  • Dataset JSON-LD on topic pages for Google Dataset Search
  • Monitor Google Search Console for entity page indexing and click-through rates