工程日記・第二十四天:衝刺 10K 索引 ——4 波改造、2,433 新 URL、Hub 鏈接重構
今天開始時我們有 8,000 個被 Google 索引的頁面——從峯值 9,200 跌下——CEO 指令:拉到 10,000+。診斷髮現一個被遺漏的 sitemap 入口(Industry Insights 頁面已存在但 sitemap.ts 沒收錄)、IndexNow 類別錯配,以及三條高 ROI 的程序化 SEO 縫隙。我們用一天完成了四波協同改造:sitemap 修復 + GEO 同步、三種新程序化頁面類型(運營商×飛機、空腿×機型類別、機場+飛機 FAQ)、E-E-A-T schema 升級(Person、NewsArticle、SpecialOffer),以及從四個 hub 頁向下鏈接到約 2,400 個子頁。最終:+2,433 個 sitemap URL、約 2.6 萬 hreflang 候選、261/261 測試全綠。
起點:8,000 索引,目標 10,000
今天開始時目標很清晰:把 Google 索引頁面從 8,000 拉回 10,000+。幾周前峯值是 9,200——掉下來不是懲罰,只是發現延遲加上幾個技術問題積累的結果。
診斷花了 30 分鐘,過了兩個 GSC 導出包和一次 sitemap 審計。根因:
- 6,336 個頁面"已發現——當前未索引"(佔未索引的 76%)。Google 自然爬蟲積壓,隨站點質量穩定會自愈。
- 726 個頁面 hreflang/canonical 錯配,驗證中。
- 203 個被 robots.txt 錯誤攔截,驗證移除中。
- 40 個locale 重複路徑(
/ja/ja/、/pt/pt/...),中間件已 301 重定向。 - 1 個致命遺漏:Industry Insights 模塊(103 條洞察 × 11 語言 ≈ 1,133 頁)兩週前已部署,但從未加入 sitemap.ts。頁面存在,但 Google 不知道它們存在。
把診斷轉成四波計劃,當天全部上線。
第一波 —— Sitemap 修復 + GEO 同步(+121 URL,約 1,331 hreflang 候選)
當天 ROI 最高的單一動作就是 sitemap.ts 30 行新增:一個 getAllInsightSlugs() 循環 + type/daily/weekly 聚合。一次提交 121 個新 URL。
GEO(生成引擎優化,讓 ChatGPT/Claude/Perplexity 能引用)層面,擴展 generate-llms-full.ts 構建腳本,加入完整 Industry Insights 索引表:103 條帶日期、類型、置信度、實體、URL。重生成的 llms-full.txt 1,523 行 / 127 KB。
IndexNow 已有 industry-insights 類別(之前 commit 加的)。部署後數分鐘內向 Bing/Yandex/Naver/Seznam 推完了 121 URL。
第二波 A —— 運營商 × 飛機配對頁(+1,798)
接下來一個問題:我們有 54 個運營商和 161 架飛機,能否在不產生薄頁的前提下生成有意義的兩兩配對頁?全笛卡爾積是 8,694——絕大多數會被 Google 當垃圾處理。
答案是 operator-aircraft-match.ts 裏的雙層匹配:
- 第一層 —— "Operates":運營商的
fleetTypes數組與飛機名 fuzzy 匹配。強事實信號。406 頁,不設上限。 - 第二層 —— "Compatible":飛機類別與運營商現有機隊類別重合。每運營商按
max(12, min(30, opCategories.size × 12))上限取 top,按製造商匹配 + 詳細頁面丰度評分。1,392 頁。
合計 1,798 個高質量配對頁,路由 /insights/operators/[slug]/aircraft/[aircraftSlug]。每頁含 5 級麪包屑、FAQPage 富結果、tier 配色徽章(Operates 綠色,Compatible 冰藍)、按運營商類型加權的價格區間、飛機規格表。
我們刻意沒有複用 EntityPageShell——那個組件是爲單實體 + 3 級麪包屑設計的。配對頁本質是雙實體,所以直接拼裝 sub-components(MetricCard、InsightSection、CoBrandingBadge)。少一層抽象,對數據更誠實。
第二波 B —— 空腿 × 機型類別成本頁(+223)
基礎 /empty-legs/[slug] 頁展示一條航線 commonAircraft 混合的整體價。但搜索意圖"[城市對] 空腿 [light/midsize/heavy/ultra] 機型——多少錢?"沒被直接回答。
empty-leg-cost-match.ts 取 62 路線 × 4 類別,按航程過濾掉不可行組合(light jet 飛不動 3,300 海里的新加坡-悉尼空腿),計算:
- 標準包機價 = 類別每小時市價 × 飛行時間
- 空腿價 = 標準價 × 30–55% 折扣區間
- 按類別典型容量(light=6、mid=8、heavy=12、ultra=14)算的人均價
- 該類別中能真正飛這條航線的 top 3 機型
- "fit note" 明確告訴用戶某類別在該航線上是過剩配置(重型機飛 1,000 海里短途)還是不足——不製造虛假信心
結果 223 頁,路由 /empty-legs/[slug]/[category],category ∈ {light-jet, midsize-jet, heavy-jet, ultra-long-range}。URL slug 故意冗長,因爲"light jet empty leg cost"是真實的長尾搜索詞,理應出現在 URL 中。
第二波 C —— 機場 + 飛機 FAQ 實體頁(+249)
Search Console 數據顯示大量未被回答的搜索意圖:"Teterboro 有哪些 FBO"、"Phenom 300 包機多少錢一小時"。數據我們都有——跑道長度、FBO 數量、每小時費率、航程——但沒有專門的 FAQ 頁配 FAQPage JSON-LD。
兩個新生成器:
- airport-faq.ts——每個機場 7 個問答:ICAO/IATA 代碼、跑道長度與機型類別准入(含跑道→類別啓發式)、FBO 列表 + 服務摘要、海關可用性、24/7 運營、熱門航線、附近景點。全部從
FeaturedAirport+getFBOsByAirport數據派生。 - aircraft-faq.ts——每架飛機 7 個問答:每小時費率 × 5 小時行程價、按航程區間舉的真實路線例子、客艙規格、哪些運營商飛此機型(複用第二波 A 的
getAircraftOperatorMatches)、同類相似機型、製造商 + 速度、預訂流程。
第二波 A 解鎖了第二波 C:飛機 FAQ 的"Which operators fly the Global 7500?"問題答案直接來自配對匹配數據——"NetJets、Flexjet、VistaJet、XO 和 Global Jet Capital,外加另外 12 家"——不是憑空造的列表。
第三波 —— GEO 護城河:Person、NewsArticle、SpecialOffer(+12 URL,285 頁 schema 升級)
第三波的頁面增量小(12 個新 URL)。Schema 升級影響很大。
從 blog/posts*.ts grep 出 6 個獨特作者:VOLO Editorial、CTO、Engineering、Aviation、Strategy、Leadership。不是 84 個個人 persona——6 個團隊實體,誠實,避免造假。
每位:
/about/authors/[slug]—— Person schema 檔案頁,含jobTitle、worksFor、knowsAbout、sameAs。穩定的@idURL 讓其他 schema 能引用此 Person。/blog/by/[author]—— CollectionPage + ItemList schema 歸檔頁,含此作者全部文章。
博客詳情頁拿到一次低調但深遠的升級:內聯 articleJsonLd 對象被 <NewsArticleJsonLd> 組件替換。@type 從 ["Article", "BlogPosting"] 升級爲 ["NewsArticle", "BlogPosting"]——獲得 Google News Top Stories 入選資格。author 通過 getAuthorByName(post.author) 解析爲真實 Person,author.@id 鏈向 /about/authors/[slug]。這纔是搜索算法真正在找的 E-E-A-T 信號。
空腿頁拿到 SpecialOfferJsonLd:含 priceSpecification、LimitedAvailability 和嵌入 eligibleTransactionVolume 的標準包機參考價(schema.org 的 Offer 類型沒有一等的"% off"字段)。62 個父頁 + 223 個類別子頁 = 285 個新 Offer 資格 URL。
內部鏈接 —— 4 個 Hub 向下到 ~2,400 個子頁
給 sitemap 加 URL 不會自動產生鏈接權重。這 2,400 個新子頁只有麪包屑作爲入站鏈接。下一關:每個 hub 頁都要展示其子頁。
| Hub | 展示 | 形式 |
|---|---|---|
/insights/operators/[slug] | up to 8 張 "Aircraft Operated" 卡 | 3 列網格、tier 配色徽章 |
/empty-legs/[slug] | 1-4 張 "Browse by Aircraft Category" 卡 | 4 列網格、FAQ 之前 |
/fleet/[slug] | up to 6 張 "Operated By" 卡 + FAQ 鏈接 | 2 列網格 + 強調色卡片 |
/airports/[slug] | "See all FAQs" 鏈接 | FAQ section 末尾內聯 |
~2,090 條內部鏈接,從權威 hub 流向程序化子頁。PageRank 跟着數據走。
Slug 系統的舊債
內部鏈接工作浮出一箇舊設計問題。/insights/operators/[slug] 用從 AviGo 月度飛行報告提取的 slug(如 "netjets-aviation")。/insights/operators/[slug]/aircraft/[aircraftSlug] 配對頁用 operators.ts 主表 slug(如 "netjets")。同一 URL 家族——完全不同的 slug 系統。
修復是 operator-aircraft-match.ts 裏一個小橋:
export function resolveOperatorSlugFromName(operatorName: string): string | null {
const target = operatorName.toLowerCase().trim();
const operators = getAllOperators();
const exact = operators.find((o) => o.name.toLowerCase() === target);
if (exact) return exact.slug;
const partial = operators.find(
(o) => target.includes(o.name.toLowerCase()) || o.name.toLowerCase().includes(target),
);
return partial?.slug ?? null;
}
實體頁把運營商名傳過這個函數得到主 slug,用於出向配對頁鏈接。兩個 slug 系統都還在——但在鏈接邊界做了橋接,而不是假裝它們是同一個東西。
收尾 —— 驗證時發現的兩個真實 bug
- Fleet 詳情頁有兩條渲染分支。199 架飛機裏 184 是 catalog-only,提前 return 走
<CatalogDetailPage>。第一版"Operated By"section 只落在 detailed 分支——絕大多數飛機不可見。修復:在兩條分支都渲染。 - IndexNow 沒有
operator-entity類別。34 個/insights/operators/[slug]hub 頁不在任何 IndexNow 類別裏,只能通過手動urls: [...]push。現在它們有自己的 case,並且進了"all"——週一 cron 會自動覆蓋。
最終數字
| 指標 | 之前 | 之後 |
|---|---|---|
| Sitemap URL(英文) | 1,750 | 4,195 |
| Hreflang 候選(× 11 語言) | ~19,250 | ~46,000 |
| 使用中的 JSON-LD 類型 | 17 | 21(+ Person、NewsArticle、SpecialOffer、IndustryInsight) |
| IndexNow 類別 | 13 | 19 |
[locale]/ 下程序化 SEO 新頁面類型 | 0 | 6 |
| Hub→子頁內部鏈接 | 0 | ~2,090 |
| 測試 | 261/261 | 261/261 |
從 8,000 到 10,000 索引頁面不會一夜發生——Google 通常需要 6-8 周消化 sitemap 並爬完長尾。但我們能控制的部分都已就位:頁面存在、sitemap 聲明它們、IndexNow 推給 4 個引擎、hub 頁向下鏈接、schema 發出權威信號。
下次會換種方式做的事
- 更早發現 sitemap 遺漏。Industry Insights 模塊兩週前發佈時沒帶 sitemap 入口。解決方案:測試套件加一個 sitemap 覆蓋斷言——
app/[locale]/下每個有generateStaticParams的路由都必須出現在sitemap.ts中。這個斷言會在 Industry Insights 原始 commit 時把 CI 紅掉。 - 把 slug 系統類型化。同一 URL 家族兩套 slug 不應該靠"字符串匹配"修——應該用
OperatorMasterSlug和OperatorEntitySlug兩個 branded type,編譯器拒絕混用。 - IndexNow 斷言和 sitemap 入口一起加。今天有三個 commit 是"加了路由,又得想起來加 IndexNow case"。這應該是一個 TypeScript 步驟,不是三個。
第 25 天可能就是關這三個迴路。今天的話:4 個 commit 進 origin/main,Vercel 部署綠,IndexNow 在推,等 Google 做它那種緩慢、深思熟慮的工作。
常見問題
Why was the indexed page count down to 8,000 from a peak of 9,200?+
Mostly discovery lag and hreflang misconfiguration, not a penalty. Search Console showed 6,336 pages 'Discovered, currently not indexed' (Google's natural crawl backlog), 726 with hreflang/canonical mismatches under validation, 203 wrongly blocked by robots.txt, and ~40 duplicate locale paths (/ja/ja/...) being 301-redirected by middleware. The single biggest miss was that the Industry Insights module (103 insights × 11 locales = ~1,133 pages) had been deployed but never added to sitemap.ts.
What are the new programmatic SEO page types added today?+
Three. (1) Operator × Aircraft pair pages at /insights/operators/[slug]/aircraft/[aircraftSlug] — 1,798 pages with two tiers ('operates' confirmed via operator.fleetTypes, 'compatible' capped at top 30 per operator). (2) Empty Leg × Category cost pages at /empty-legs/[slug]/[category] — 223 pages (light-jet/midsize-jet/heavy-jet/ultra-long-range), range-filtered so we don't generate light-jet pages for 3,300nm routes. (3) Airport + Aircraft FAQ entity pages at /airports/[slug]/faq and /fleet/[slug]/faq — 249 pages with 7 data-driven Q&As each, generated from existing entity data plus Wave 2A operator matches.
What does the GEO (Generative Engine Optimization) work add?+
Three schema upgrades. (1) Person schema on 6 author bio pages establishes E-E-A-T entities; each NewsArticle on a blog post now points its author.@id to the corresponding /about/authors/[slug] URL — search engines can stitch the author identity across articles. (2) NewsArticle (replacing generic Article) on all 84 blog posts unlocks Top Stories carousel eligibility. (3) SpecialOffer on all 62 empty-leg parent pages plus the 223 category sub-pages publishes structured pricing with priceSpecification and a standard-charter reference price — eligible for Google's offer rich result.
How do hub pages link to the new sub-pages?+
Each of four hub page types now surfaces relevant sub-pages: (1) /insights/operators/[slug] shows up to 8 'Aircraft Operated' cards linking to pair pages; (2) /empty-legs/[slug] shows 1–4 'Browse by Aircraft Category' cards; (3) /fleet/[slug] shows up to 6 'Operated By' cards plus a FAQ link card; (4) /airports/[slug] adds a 'See all FAQs' link inside the existing FAQ section. Total: ~2,090 hub-to-sub-page internal links — concentrated PageRank flow, not just sitemap presence.
What was the slug-system bug that surfaced during internal linking?+
The /insights/operators/[slug] entity hub uses data-extracted slugs (e.g. 'netjets-aviation' from monthly flight reports), while /insights/operators/[slug]/aircraft/[aircraftSlug] pair pages use the operators.ts master list slugs (e.g. 'netjets'). They look like the same URL family but they're not. We bridged the two with resolveOperatorSlugFromName() — a case-insensitive name match that lets the entity page resolve to the master slug for outgoing pair-page links. A reminder that 'one URL pattern, two slug systems' is its own kind of debt.
準備好飛行了嗎?幾秒鐘獲取個性化包機報價。
訂閱資訊
空腿航班優惠、新航線與航空洞察,直達您的郵箱。