很多人在架設部落格時,會把部落格放在子網域(blog.example.com)而不是主網域(example.com)。這是因為多數部落格平台和 CMS 預設就以子網域運作,部署起來最省事。

但如果你在乎 SEO,可能要重新考慮這個選擇。

為什麼要放在子目錄?

SEO 優勢

子網域在搜尋引擎眼中是獨立的網站,必須從頭累積權重。而子目錄(example.com/blog)繼承主網域累積的所有域名權重,理論上能更快獲得好的排名。

Google 官方曾表示子網域不會被視為獨立實體,但實際數據顯示子目錄的表現普遍更好。ButterCMS 有一篇文章指出,子目錄的 SEO 效果比子網域高出約 40%。

我的個人經驗也印證了這一點。將一個部落格從子網域遷移到子目錄後,在沒有發表新文章、也沒有推廣的情況下,有機流量和搜尋排名在幾週內就有明顯提升。

子目錄的缺點

架構更複雜。多數部落格平台原生支援子網域,要改造成子網域需要多一步處理。不過一旦完成,整體維護成本其實不高。

實作方案:Cloudflare Workers

以下是我的完整設定流程。範例中假設:

  • 主網站(example.com)託管在 Render(靜態主機)
  • 部落格(blog.example.com)託管在 Vercel(Next.js)

流程中的多數步驟適用於任何主機提供商,關鍵操作都在 Cloudflare Dashboard 完成。

注意: Cloudflare 時常更新後台介面和路由。如果操作畫面與本文截圖不符,使用後台的搜尋功能找到對應項目即可。

步驟一:設定主網站的 DNS 紀錄

進入 Cloudflare Dashboard,點選你的網域:

  1. SSL/TLS → Overview → Configure,選擇 Full
  2. 進入 DNS → DNS Records,新增以下紀錄(以 Render 為例):
Type Name Target Proxy status TTL
CNAME @ my-site.onrender.com Proxied Auto
CNAME www my-site.onrender.com Proxied Auto
CNAME api my-api.onrender.com Proxied Auto

務必將 Proxy status 設為 Proxied,且不要新增萬用字元紀錄(如 *.example.com)。

步驟二:設定部落格的 DNS 紀錄

確認部落格已可在子網域正常存取後,再新增一筆 DNS 紀錄:

Type Name Target Proxy status TTL
CNAME blog cname.vercel-dns.com Proxied Auto

步驟三:調整部落格的路由設定

在部落格的 next.config.jsnext.config.mjs 中加入 basePath: "/blog"

/** @type {import('next').NextConfig} */
const nextConfig = {
  basePath: "/blog",
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "imagedelivery.net",
      },
    ],
  },
};

export default nextConfig;

請確認部落格的路由目前指向 / 而非 /blog不應該有包含 /blog 的路由

步驟四:建立 Cloudflare Worker

在 Cloudflare Dashboard:

  1. 點選 Workers & Pages
  2. 點選 Create → Create Worker
  3. 選擇 Start with Hello World!,命名為 blog-worker,點選 Deploy

部署完成後,進入 Worker 編輯器,將程式碼替換為以下內容:

export default {
  async fetch(request, env, ctx) {
    async function MethodNotAllowed(request) {
      return new Response(`Method ${request.method} not allowed.`, {
        status: 405,
        headers: {
          Allow: "GET",
        },
      });
    }
    if (request.method !== "GET") return MethodNotAllowed(request);

    const url = new URL(request.url);

    const originUrl = url.toString().replace(
      'https://example.com/blog',
      'https://blog.example.com/blog'
    ).replace(
      'https://www.example.com/blog',
      'https://blog.example.com/blog'
    );

    const originPage = await fetch(originUrl);

    const newResponse = new Response(originPage.body, originPage);
    return newResponse;
  },
};

替換掉範例中的網址為你自己的網域。儲存後,點選版本 ID 的雜湊值(如 b30983e0),再點選 Apply 來部署。

步驟五:連接 Worker 與路由

回到 Cloudflare Dashboard,進入 Worker Routes

  1. 新增第一條路由:

    • Route: example.com/blog/*
    • Worker: blog-worker
  2. 新增第二條路由(處理靜態資源):

    • Route: example.com/blog/_next/static/*
    • Worker: blog-worker

設定完成後,理論上就能透過 example.com/blog 存取原本在 blog.example.com 的部落格了。

步驟六:防止子網域被搜尋引擎索引

因為部落格現在同時存在於子網域和子目錄,必須確保搜尋引擎只索引子目錄,否則會因重複內容而傷害 SEO。

更新 Next.js 設定

next.config.js 中加入 headers 設定,讓子網域的所有頁面都回傳 noindex

/** @type {import('next').NextConfig} */
const nextConfig = {
  basePath: "/blog",
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "imagedelivery.net",
      },
    ],
  },
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'X-Robots-Tag',
            value: 'noindex, nofollow',
          },
        ],
      },
    ];
  },
};

export default nextConfig;

更新 Cloudflare Worker

修改 Worker 程式碼,在轉發回應時移除 x-robots-tag header:

export default {
  async fetch(request, env, ctx) {
    async function MethodNotAllowed(request) {
      return new Response(`Method ${request.method} not allowed.`, {
        status: 405,
        headers: {
          Allow: "GET",
        },
      });
    }
    if (request.method !== "GET") return MethodNotAllowed(request);

    const url = new URL(request.url);

    const originUrl = url.toString().replace(
      'https://example.com/blog',
      'https://blog.example.com/blog'
    ).replace(
      'https://www.example.com/blog',
      'https://blog.example.com/blog'
    );

    const originPage = await fetch(originUrl);

    let newResponse = new Response(originPage.body, originPage);

    newResponse.headers.delete("x-robots-tag");

    return newResponse;
  },
};

這樣子目錄收到的回應會是乾淨的,不帶 noindex header;而子網域則維持 X-Robots-Tag: noindex, nofollow

步驟七:驗證結果

驗證子網域未遭索引

開啟你部落格的 Vercel 部署網址(https://vercel.com/my-projects/...),在瀏覽器開發者工具的 Network 分頁中,確認回應 header 沒有 X-Robots-Tag。如果沒有這個 header,表示 Next.js 設定正確。

驗證子目錄已被索引

前往 Google URL Inspection Tool,輸入你的子目錄網址(如 example.com/blog),確認 Google 已將其編入索引。

適用於 Hugo 的對應調整

如果你使用的是 Hugo 而不是 Next.js,核心概念相同,但實作細節不同:

  1. basePath 概念:Hugo 輸出靜態檔案到 public/ 目錄。如果要讓產出的 HTML 資源路徑前綴 /blog,可在 hugo.toml 中設定:
[permalinks]
  blog = "/blog/:slug"
  1. Cloudflare Worker 不變:不管前端用什麼框架,Worker 的 URL 置換邏輯完全相同。

  2. 404 處理:Hugo 的靜態頁面需要確認所有內部連結都已更新為 /blog/ 前綴,否則圖片、CSS、JS 會404。

結語

用 Cloudflare Workers 將部落格從子網域遷移到子目錄,技術上可行且效果顯著。整個流程的核心優點是:

  • 不改變既有主機設定:部落格依然託管在原本的平台上
  • DNS 集中管理:所有流量經過 Cloudflare,可擴充其他規則
  • SEO 提升:子目錄繼承主網域權重,長期來說值得投資

代價是初期設定需要一些時間,特別是 Worker 的路由邏輯要處理乾淨。不過一旦架好,之後的維護成本極低,是一勞永逸的方案。

如果你對自己的 Hugo 網站有 SEO 需求,值得評估是否要進行這次遷移。