[AWS x SEO対応] S3 & CloudFrontで静的ホスティングしたウェブサイトのドメイン移管 ~ 301リダイレクトを仕込む


2021/03/14
蛸壺の技術ブログ|S3 & CloudFrontで静的ホスティングしたウェブサイトのドメイン移管 ~ 301リダイレクトを仕込む

一例を挙げると、
hoge-s3-blog.jpというドメイン名を新たに取得し、既にAWS S3の静的ホスティングで運用していたブログサイト・piyo-s3-blog.comを出来るだけこれまで築き上げたSEO効果へのダメージを少なめで移管させたい...そんなときに使うのが301リダイレクトです。

この時に、AWS S3とCloudFrontを用いたCDN方式のウェブサイトは、通常のレンタルサーバーとは仕組みが違うので、定石である
.htaccessファイルを使って301リダイレクトが出来ません。

ということで今回はAWS S3ウェブサイトのお引越しの際特有の301リダイレクト方法に関して考察してみます。


301リダイレクト?

リダイレクトとは読んで字のごとく、特定のURLを踏んだら、別のURLアドレスへ遷移先を変えてくれる機能で、通常HTTPレスポンス・ステータスコードでは300番台を使います。

特にドメインのリニューアルに伴うリダイレクトで重要なのは、以下の301と302です。

            
            301 Moved Permanently:
    リクエストされたリソースのURLが永遠に変更されたことを示します。
    レスポンスで新しいURLが与えられます。
302 Found:
    リクエスト元のURLが一時的に変更されたことを示します。
    リクエスト元のURLは今後さらに変更される可能性もあるので、
    クライアントは引き続き同じURLを使用するべき、と解釈されます。
        
どちらも結果的にリダイレクトしてくれることには変わりないのですが、「全面的にサイトを移管する」301を使ったほうがよりGoogleなどのクローラーに適切に処置してもらえる可能性があります(今は302を使っても大差はないと言われていますが...)。

では以降では、静的ホスティングしたAWS S3サイトをLambda@Edgeを使って柔軟にドメイン移管させるやり方をやってみます。

なお、サイトを新しいドメインに完全移管する場合には、
リダイレクトの設定を完了した後でGoogleクローラーにインデックスを催促の順序で行うようにしましょう。先にページ構造変更前のsitemap.xmlをSearch Consoleにリクエストしてから、ドメイン間のリダイレクト設定しても意味が無いことに留意してください。


Lambda@Edgeでリダイレクトを自動化

では本記事では、AWS S3上で静的ホスティングで運用しているウェブサイトpiyo-s3-blog.comのドメインからhoge-s3-blog.jpというURLに引っ越しすることを考えてみます。

とりあえず前提として先にpiyo-s3-blog.comで配信しているS3バケット内のコンテンツを丸ごと、hoge-s3-blog.jpで配信するS3バケット内へコピーしておいた状態にしておき、リダイレクト先が存在するようにしておきます。

この時にGoogleアナリティクスやタグマネージャーなどの特定サイト監視用のjavascriptスニペットはhtmlリソースから外したり、Googleアドセンスのような参加中のアフィリエイトプログラムで、ドメインごとに審査があるようなもののリンクは消去しておくと、まさかの時のペナルティーを避けることが出来るので、移管するサイトによっては色々とサイトコピー時のケアを考える必要があります。

AWS S3から柔軟なHTTPレスポンス操作をするのには
Lambda@Edgeを利用します。一見、Lambdaの特殊な機能なのかなとも思うのですが、中身はAWS CloudFrontの機能の一部であり、AWS Lambdaと連帯して、コンテンツ配信ネットワーク(CDN)によって発生したイベントごとに対応したコードを実行してくれる機能です。

Lambda@Edgeは元々、CloudFrontの得意な今回のような静的なコンテンツ配信を行う機能に、Lambdaが力添えをしてURLのプレフィックス処理など動的な制御を行えるようにしたサービスです。

前回ではLambda@Edgeの応用例として、クライアント側からのレスポンスの値を修正してサーバー側(CloudFront側)に流すテクニックを特集していました。

他にもLambda@Edgeは色々と魅力的なユースケースがあるようですが、今回はさらなる応用例として301リダイレクトの手順を以降で説明していきます。

Lambda関数の作成

基本的に前回説明したLambda関数の作成手順と全く同じです。Lambda関数の手順が不明であれば、前回の記事の内容を一度じっくりご確認ください。

前回からの修正点としては
index.jsの中身だけですのでここを重点的に説明します。

今回の例として、引っ越し元のドメインである
piyo-s3-blog.comのコンテンツを丸ごと引っ越し先の新しいドメイン・hoge-s3-blog.jpへ特定のURLをリクエストされた場合のみ、選択的に301リダイレクトすることを考えてみたいと思います。

模式図的に表すと以下のようになります。

合同会社タコスキングダム|蛸壺の技術ブログ

なお、
前回'/index.html'を省略できるようにLambda@Edgeの設定を反映済みとして、とあるクライアントがpiyo-s3-blog.com/piyoへのアクセスを試みた場合には、そのままpiyo-s3-blog.comのpiyo以下にあるindex.htmlの内容でレスポンスを返し、また別のクライアントがpiyo-s3-blog.com/fugaへアクセスした場合には、リクエストをhoge-s3-blog.jp/fugaへリダイレクトさせ、hoge-s3-blog.jpのfuga/index.htmlの内容を返すようにすることを考えてみます。

するとLambda@Edgeのコードは以下のようになります。

            
            const convertMap = {
    '/fuga/index.html': 'https://hoge-s3-blog.jp/fuga/index.html',
    //👇リダイレクトしたいURLを連想配列として追加...
    '/mofu/index.html': 'https://hoge-s3-blog.jp/mofu/index.html',
    //...
};

exports.handler = async (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const uri = request.uri;
    const uriMod = uri.replace(/\/$/, '\/index.html').replace(/\/([^\.\/]+)$/, '/$1/index.html');
    const val = convertMap[uriMod];

    if(val !== undefined) {
        console.log('+++ Redirect To +++');
        console.log(val);
        const redirectRes = {
            status: '301',
            statusDescription: 'Moved Permanently',
            headers: {
                location: [{
                    key: 'Location',
                    value: val,
                }],
            },
        };
        return callback(null, redirectRes);
    } else {
        console.log('+++ Direct To +++');
        console.log(uriMod);
        request.uri = uriMod;
        return callback(null, request);
    }
};
        
ちなみにこのLambda@Edgeをデプロイするときにトリガーするターゲットはpiyo-s3-blog.comをホスティングしているClodFrontディストリビューションを指定します。

Lambda関数の動作確認

軽い動作確認用に適当な名前を付けてテストを作ってみます。

まずはリダイレクトしないケースのリクエスト用テストスニペットを以下の用に作成します。

            
            {
  "Records": [
    {
      "cf": {
        "config": {
          "distributionId": "EXAMPLE"
        },
        "request": {
          "headers": {
            "host": [
              {
                "key": "Host",
                "value": "d123.cf.net"
              }
            ],
            "user-name": [
              {
                "key": "User-Name",
                "value": "CloudFront"
              }
            ]
          },
          "clientIp": "1111:2222:3333:4444",
          "uri": "/piyo",
          "method": "GET"
        },
        "response": {
          "status": "200",
          "statusDescription": "OK",
          "headers": {
            "x-cache": [
              {
                "key": "X-Cache",
                "value": "Hello from Cloudfront"
              }
            ]
          }
        }
      }
    }
  ]
}
        
これをテストすると、以下のように通常のアクセスと変わらないレスポンスが得られるはずです。

            
            Response
{
  "headers": {
    "host": [
      {
        "key": "Host",
        "value": "d123.cf.net"
      }
    ],
    "user-name": [
      {
        "key": "User-Name",
        "value": "CloudFront"
      }
    ]
  },
  "clientIp": "1111:2222:3333:4444",
  "uri": "/piyo/index.html",
  "method": "GET"
}

Function Logs
START RequestId: ************************** Version: $LATEST
2021-03-14T08:24:23.279Z ****************** INFO +++ Direct To +++
2021-03-14T08:24:23.282Z ****************** INFO /piyo/index.html
END RequestId: **************************
REPORT RequestId: ************************* Duration: 5.96 ms Billed Duration: 6 ms Memory Size: 128 MB Max Memory Used: 65 MB Init Duration: 148.17 ms
        
では次にリダイレクトするテストケースを作成します。先程のレスポンスの内容でuriフィールドを/fugaに変えるだけです。

            
            {
  "Records": [
    {
      "cf": {
        //...
          "uri": "/fuga",
        //...
        
これでコードのテストを実行すると、以下のようなリダイレクトした結果のレスポンスを得ると成功です。

            
            Response
{
  "status": "301",
  "statusDescription": "Moved Permanently",
  "headers": {
    "location": [
      {
        "key": "Location",
        "value": "https://hoge-s3-blog.jp/fuga/index.html"
      }
    ]
  }
}

Function Logs
START RequestId: ********************* Version: $LATEST
2021-03-14T08:17:18.965Z ************* INFO +++ Redirect To +++
2021-03-14T08:17:18.965Z ************* INFO https://hoge-s3-blog.jp/fuga/index.html
END RequestId: *********************
REPORT RequestId: ******************** Duration: 2.44 ms Billed Duration: 3 ms Memory Size: 128 MB Max Memory Used: 65 MB
        
動作を確認したら本番用のLambdaをデプロイすると上手くリダイレクトすると思います。


まとめ

今回はAWS S3 & CloudFrontでコンテンツ配信しているパターンのウェブサイトで、どうリダイレクトを実現するのかをLambda@Edgeで行ってみました。

このテクニックを使うと、ウェブサイトのドメインを全面的に移行する際に、これまでのサイト内のリンクを貼ってもらっていたサイトユーザーにも自動で移動後のページに誘導できるようになります。

とはいえ、引っ越し元のドメインが有効期限が切れるまでには、サイトを移管した旨が伝わるようにしっかり事後処理する必要があることには変わりませんので、サイトの全面的なリニューアルには十分余裕をもったスケジュールで臨みたいものです。


参考サイト

SEOへ悪影響を与えないドメインの変更方法[SEOへ悪影響を与えないドメインの変更方法](https://ferret-plus.com/12897)

Handling Redirects@Edge

サイトリニューアル時のURLリダイレクトをLambda@Edgeで実現する

CloudFrontでLambda@Edgeを使ってHTTPリダイレクトを設定する

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。