Engineering Diary, Day 23: Building a Business Aviation Intelligence Center — 103 Insights, 11 Languages, 1,331 New Indexed Pages
The Opportunity: Exclusive Data Needs a Home
VOLO's Insights Hub already had 200+ entity pages, 7 monthly reports, and 26 page types covering airports, aircraft, operators, and routes. But all of it was structured market data — tables, rankings, trend charts. What we didn't have was analysis. The kind of deep, cross-referenced intelligence that tells you not just "OMDB traffic dropped 97%" but why, what it means for brokers, and which alternative airports are absorbing the overflow.
Our CEO acquired a dataset of 103 curated business aviation insights — each one cross-validated between AviGo flight data and knowledge graph business events. Every insight has a confidence rating (1-3 stars), a type classification, and entity references. All in Chinese. The directive: "Build an information center with industry-wide data and proactive insights. Make it massive for SEO and GEO."
Architecture: From JSON to 1,331 Pages
The raw data was a single JSON file with 103 entries. The challenge was transforming this into a fully-indexed, multilingual, cross-linked module that integrates seamlessly with our existing Insights infrastructure.
Data Layer (3 files)
We built a three-layer data architecture:
- types.ts — IndustryInsight, MultilingualInsightText (11 locales), NormalizedEntity, DailyDigest, WeeklyDigest type definitions
- data.ts — Raw JSON processing with entity normalization (ICAO airport codes, aircraft models, operator names), garbage filtering (Chinese descriptive text fragments mixed in with entities), confidence extraction from star characters, and a 103-entry slug mapping table
- index.ts — 14 query functions including getInsightBySlug, getInsightsByType, getInsightsForEntity, getDailyDigests, getWeeklyDigests, and getRelatedInsights (scoring algorithm: shared entities 3x weight + same type 1x + date proximity 0-1x)
Entity Normalization: The Hidden Complexity
The raw entities array was messy. Some entries were clean ICAO codes ("KTEB", "OMDB"), some were aircraft names ("Phenom 300", "G700"), but many were garbage — Chinese descriptive text fragments that slipped in from the knowledge graph extraction. Our normalization pipeline:
- Filter: Reject any string containing Chinese characters
- Filter: Reject strings longer than 30 characters
- Deduplicate: "Phenom" and "Phenom 1" both map to "Phenom 300"
- Classify: ICAO pattern (/^[A-Z]{4}$/) → airport, known aircraft keywords → aircraft, everything else → operator
- Enrich: 80+ ICAO codes mapped to human-readable display names ("KTEB" → "Teterboro Airport")
Semantic URL Slugs
Every insight needed a unique, SEO-optimized English slug. We manually mapped all 103 IDs to descriptive slugs:
- ID 326 →
uae-airports-92pct-decline-iran-war - ID 334 →
cannes-film-festival-aviation-pulse - ID 297 →
netjets-global-8000-domestic-route-paradox - ID 285 →
boao-forum-15-movements-deer-jet-absent
These slugs directly match long-tail search queries. Someone searching "UAE business jet traffic decline Iran war" will find our page.
11 Languages, 1,133 Translations
The CEO's directive was clear: "你有多少语种就翻译多少" (translate into every language you have). We have 11 locales.
We parallelized the translation work into 4 batches:
- English — Primary translation from Chinese originals (103 insights)
- zh-Hant + Japanese + Korean — CJK languages (parallel agent)
- French + Spanish + German — European languages (parallel agent)
- Portuguese + Russian + Arabic — Remaining languages (parallel agent)
Each translation preserves all ICAO codes, aircraft model names, operator names, numerical data, percentages, dates, and confidence ratings exactly. The result: 10 translation files (zh-Hant, en, fr, es, pt, ja, ko, de, ru, ar) totaling ~1.2MB of translated content.
The integration uses static imports with a fallback chain: native language → English → Chinese original. This means the system works even if a translation file is missing.
Page Routes: 5 Types, 121 Routes
We created 5 distinct route groups:
| Route | Count | Purpose |
|---|---|---|
| /insights/industry | 1 | Hub page with metrics, type filters, featured insights |
| /insights/industry/[slug] | 103 | Individual insight detail pages with full analysis |
| /insights/industry/type/[type] | 5 | Type aggregate pages (risk, opportunity, trend, anomaly, regulatory) |
| /insights/industry/daily/[date] | ~10 | Daily digest pages with prev/next navigation |
| /insights/industry/weekly/[week] | ~2 | Weekly summary with top entity frequency analysis |
121 routes × 11 locales = ~1,331 new indexed pages.
Components: 7 New, Reusing the Shell Pattern
We built 7 new components following our established design system (vanta black, gold accents, monospace metadata):
- InsightTypeBadge — Color-coded type labels (risk=red, opportunity=green, trend=blue, anomaly=orange, regulatory=purple)
- ConfidenceIndicator — Star rating display with optional label
- InsightCard — Card component for listing pages with type badge, confidence, entity tags, date
- InsightEntityTags — Clickable entity chips linking to airport/aircraft/operator pages (with disableLinks prop for use inside link cards)
- InsightFilterBar — Client component with type tabs and text search
- RelatedInsights — Grid of related insight cards
- InsightPageShell — Layout shell following the EntityPageShell pattern (breadcrumbs, JSON-LD, header, analysis, FAQ, related insights)
Cross-Linking: The SEO Force Multiplier
Bidirectional cross-links were critical for this module to have maximum SEO impact:
- Insight → Entity: Each insight detail page shows entity tags (airports, aircraft, operators) that link to existing entity pages
- Entity → Insight: We added an optional
industryInsightsprop to EntityPageShell. Airport pages like KTEB now show 6 related industry insights between the main content and FAQ section - Hub → Insight: The main Insights hub page (/insights) now has an "Industry Insights NEW" section with 3 featured cards
- Header navigation: Added "Industry Insights" link to the About dropdown menu across all 11 locales
This creates a dense internal link mesh: a user on the Teterboro Airport page sees insights mentioning KTEB, clicks through to learn about NetJets' Global 7500 fleet utilization, then navigates to the Global 7500 aircraft page, which links to insights about the ultra-long-range three-way race. Every click deepens engagement and signals topical authority to search engines.
SEO & Structured Data
- Article JSON-LD on every insight detail page (headline, description, datePublished, author, publisher, keywords, about entities)
- FAQ JSON-LD on hub, type, and detail pages (auto-generated questions about data sources, confidence, and entities)
- BreadcrumbList JSON-LD on all pages
- IndexNow API updated to include all 121 industry insight URLs — submitted within minutes of deployment, notifying Bing, Yandex, Seznam, and Naver
March 2026 Monthly Report
In parallel, we extracted the March 2026 monthly report from 104 HTML pages into a 748-line TypeScript data file. Key figures: 360,597 total flights (+24.82% YoY, +70.34% MoM), 11,632 daily average, NA 75.89% / EU 14.92% / ROW 9.18%. Featured route: Dallas-Houston (987 flights). Most popular model: Phenom 300 (35,225 flights). This is now the 7th monthly report in our system, extending the time series to Oct 2025 – Mar 2026.
Bug Fix: Missing Translation Key
During verification, we discovered a pre-existing bug: the ReportCard component referenced t("fleet") from the common namespace, but no such key existed. This caused the English version of the Insights hub to crash silently. We added "fleet": "Active Fleet" to the common namespace across all 11 locale files.
Technical Challenges
Turbopack vs require()
Our initial approach used try { require('./translations-xx') } catch {} for optional translation loading. This works in Node.js and webpack production builds, but Turbopack in dev mode treats unresolved require() as hard errors even inside try/catch. We switched to static imports, adding each translation file as it became available.
Server Component onClick
The InsightCard component initially had <div => e.preventDefault()}> to prevent parent Link navigation when clicking entity tags. Server Components can't have event handlers. We solved this by adding a disableLinks prop to InsightEntityTags, used inside card contexts where the entire card is already a link.
By the Numbers
| Metric | Before | After |
|---|---|---|
| Files changed | — | 48 files, +11,518 lines |
| New page routes | 106 | 227 (+121) |
| New indexed pages (est.) | 8,350 | ~9,700 (+1,331) |
| New components | 156 | 163 (+7) |
| Monthly reports | 6 | 7 (added March 2026) |
| Industry insights | 0 | 103 |
| Translations generated | — | 1,133 (103 × 11 - zh original) |
| Translation files | — | 10 files, ~1.2MB |
| Insight types | — | 5 (risk/opportunity/trend/anomaly/regulatory) |
| JSON-LD schema types | 17 | 18 (+Article for insights) |
| IndexNow URLs submitted | — | 121 |
| Tests passing | 261 | 261 |
| Build time | — | 55s (zero errors) |
What's Next
The Industry Insights module is live but still young. Next steps: automate insight ingestion (currently manual JSON), add more entity cross-references (routes, countries), build an insight RSS feed for syndication, and monitor Google indexing velocity for the 1,331 new pages. We should also consider adding visual charts to individual insight pages and expanding the dataset beyond the current March 25 – April 3 window.
The moat is deepening. No competitor in the business aviation space has a multilingual, AI-cross-validated intelligence center with this level of structured data, SEO optimization, and search engine notification infrastructure. The 103 insights we launched today are just the beginning.
Готовы к полёту? Получите персональную котировку чартера за секунды.
Будьте в курсе
Порожние рейсы, новые маршруты и аналитика авиации — прямо в вашу почту.