[{"data":1,"prerenderedAt":3070},["ShallowReactive",2],{"tag-aws":3},{"count":4,"content":5},4,[6,835,1728,1843],{"id":7,"title":8,"body":9,"category":819,"createdAt":821,"description":822,"extension":823,"index":824,"meta":825,"navigation":94,"path":826,"publish":94,"seo":827,"series":824,"seriesTitle":824,"stem":828,"tag":829,"thumbnail":833,"updatedAt":821,"__hash__":834},"articles\u002Farticles\u002Faws-sam-disconnect-dymamodb-local.md","AWS SAMで作ったLamdaコードがローカルのDynamoDBに接続できないときの対処",{"type":10,"value":11,"toc":812},"minimark",[12,16,19,30,33,44,47,50,55,58,222,233,572,575,581,584,591,594,597,602,610,625,628,632,635,753,756,762,768,802,805,808],[13,14,15],"p",{},"こんにちはjunです。最近はこの静的ブログに動的な機能を追加するために、サーバーレス環境でのサービス開発を行っています。API gateway をトリガーとしてAWS Lambdaでコードを実行してDynamoDBで内容を保存するといったよくある構成で開発をしていました。",[13,17,18],{},"AWS Lambdaはweb上でもコードを書いて実行できますが、できたらローカル上でコードを書いてテストしてからデプロイしたいです。特に更新や改修の時はローカルで検証できることが好ましいです。",[13,20,21,22,29],{},"AWSはその辺も考えており、",[23,24,28],"a",{"href":25,"rel":26},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002Fserverless-application-model\u002Flatest\u002Fdeveloperguide\u002Fwhat-is-sam.html",[27],"nofollow","AWS SAM(serverless application model)"," を用意してくれています。私も開発ではこのSAMを使用してローカルで構築後、テストして本番に適用しています。",[13,31,32],{},"ひとまずチュートリアルのデプロイができたので、DynamoDBを用いた環境をローカルに作ってコードをテストすることにしました。しかし、DynamoDBをscanする処理でいつもストップし、",[34,35,40],"pre",{"className":36,"code":38,"language":39},[37],"language-text","Inaccessible host: `127.0.0.1' at port `8000'. This service may not be available in the `ap-north-east1' region.\n","text",[41,42,38],"code",{"__ignoreMap":43},"",[13,45,46],{},"というメッセージでDynamoDBに接続できませんでした。ちなみにDynamoDBの環境はDockerを用いてローカルで構築しており、localost:8000に待機していました。DynamoDBのコンテナは起動しているのに、どうして接続できないのか悩んでいました。",[13,48,49],{},"結論を言うとDockerのネットワークの設定が間違っていたからです。詳細を解説します。",[51,52,54],"h2",{"id":53},"lambdaのコードとdynamodbのコンテナ設定","LambdaのコードとDynamoDBのコンテナ設定",[13,56,57],{},"DynamoDBはこちらのDocker imageを使用して、以下の設定を用いていました。（参考サイトからのコピペです...)",[34,59,64],{"className":60,"code":61,"filename":62,"language":63,"meta":43,"style":43},"language-yaml shiki shiki-themes material-theme-ocean","version: \"3\"\n\nservices:\n  dynamodb-local:\n    container_name: dynamodb-local\n    image: amazon\u002Fdynamodb-local\n    ports:\n      - 8000:8000\n    command: -jar DynamoDBLocal.jar -dbPath \u002Fdata -sharedDb\n    volumes:\n      - .\u002Fdata:\u002Fdata\n    networks:\n      - lambda-local\nnetworks:\n  lambda-local:\n    external: true\n","docker-compose.yml","yaml",[41,65,66,89,96,105,112,123,134,142,151,162,170,178,186,194,202,210],{"__ignoreMap":43},[67,68,71,75,79,82,86],"span",{"class":69,"line":70},"line",1,[67,72,74],{"class":73},"s-wAU","version",[67,76,78],{"class":77},"sAklC",":",[67,80,81],{"class":77}," \"",[67,83,85],{"class":84},"sfyAc","3",[67,87,88],{"class":77},"\"\n",[67,90,92],{"class":69,"line":91},2,[67,93,95],{"emptyLinePlaceholder":94},true,"\n",[67,97,99,102],{"class":69,"line":98},3,[67,100,101],{"class":73},"services",[67,103,104],{"class":77},":\n",[67,106,107,110],{"class":69,"line":4},[67,108,109],{"class":73},"  dynamodb-local",[67,111,104],{"class":77},[67,113,115,118,120],{"class":69,"line":114},5,[67,116,117],{"class":73},"    container_name",[67,119,78],{"class":77},[67,121,122],{"class":84}," dynamodb-local\n",[67,124,126,129,131],{"class":69,"line":125},6,[67,127,128],{"class":73},"    image",[67,130,78],{"class":77},[67,132,133],{"class":84}," amazon\u002Fdynamodb-local\n",[67,135,137,140],{"class":69,"line":136},7,[67,138,139],{"class":73},"    ports",[67,141,104],{"class":77},[67,143,145,148],{"class":69,"line":144},8,[67,146,147],{"class":77},"      -",[67,149,150],{"class":84}," 8000:8000\n",[67,152,154,157,159],{"class":69,"line":153},9,[67,155,156],{"class":73},"    command",[67,158,78],{"class":77},[67,160,161],{"class":84}," -jar DynamoDBLocal.jar -dbPath \u002Fdata -sharedDb\n",[67,163,165,168],{"class":69,"line":164},10,[67,166,167],{"class":73},"    volumes",[67,169,104],{"class":77},[67,171,173,175],{"class":69,"line":172},11,[67,174,147],{"class":77},[67,176,177],{"class":84}," .\u002Fdata:\u002Fdata\n",[67,179,181,184],{"class":69,"line":180},12,[67,182,183],{"class":73},"    networks",[67,185,104],{"class":77},[67,187,189,191],{"class":69,"line":188},13,[67,190,147],{"class":77},[67,192,193],{"class":84}," lambda-local\n",[67,195,197,200],{"class":69,"line":196},14,[67,198,199],{"class":73},"networks",[67,201,104],{"class":77},[67,203,205,208],{"class":69,"line":204},15,[67,206,207],{"class":73},"  lambda-local",[67,209,104],{"class":77},[67,211,213,216,218],{"class":69,"line":212},16,[67,214,215],{"class":73},"    external",[67,217,78],{"class":77},[67,219,221],{"class":220},"sbqyR"," true\n",[13,223,224,225,228,229,232],{},"上記の",[41,226,227],{},"- 8000:8000"," の通りlocalhostの8000にDynamoDBのエンドポイントが待機しています。\n",[41,230,231],{},"docker-compose up","してこの記述の通り、Lambdaのコードで以下のように記述しました。",[34,234,239],{"className":235,"code":236,"filename":237,"language":238,"meta":43,"style":43},"language-js shiki shiki-themes material-theme-ocean","const AWS = require('aws-sdk')\nlet response;\n\nvar dynamoOpt = {\n    endpoint: \"http:\u002F\u002Flocalhost:8000\",\n};\n\nexports.lambdaHandler = async (event, context) => {\n    let params = {\n            TableName:'table',\n        };\n\n        try{\n            const result = await dynamodb.scan(params).promise()\n            return {\n                statusCode: 200,\n                body: JSON.stringify(result.Items),\n            };\n        }catch (e){\n            return {\n                statusCode: 500,\n                body: e.message,\n            };\n        }\n}\n","app.js","js",[41,240,241,272,283,287,300,318,323,327,362,374,390,395,399,408,445,452,465,495,501,519,526,538,555,560,566],{"__ignoreMap":43},[67,242,243,247,251,254,258,261,264,267,269],{"class":69,"line":70},[67,244,246],{"class":245},"sJ14y","const",[67,248,250],{"class":249},"s0W1g"," AWS ",[67,252,253],{"class":77},"=",[67,255,257],{"class":256},"sdLwU"," require",[67,259,260],{"class":249},"(",[67,262,263],{"class":77},"'",[67,265,266],{"class":84},"aws-sdk",[67,268,263],{"class":77},[67,270,271],{"class":249},")\n",[67,273,274,277,280],{"class":69,"line":91},[67,275,276],{"class":245},"let",[67,278,279],{"class":249}," response",[67,281,282],{"class":77},";\n",[67,284,285],{"class":69,"line":98},[67,286,95],{"emptyLinePlaceholder":94},[67,288,289,292,295,297],{"class":69,"line":4},[67,290,291],{"class":245},"var",[67,293,294],{"class":249}," dynamoOpt ",[67,296,253],{"class":77},[67,298,299],{"class":77}," {\n",[67,301,302,305,307,309,312,315],{"class":69,"line":114},[67,303,304],{"class":73},"    endpoint",[67,306,78],{"class":77},[67,308,81],{"class":77},[67,310,311],{"class":84},"http:\u002F\u002Flocalhost:8000",[67,313,314],{"class":77},"\"",[67,316,317],{"class":77},",\n",[67,319,320],{"class":69,"line":125},[67,321,322],{"class":77},"};\n",[67,324,325],{"class":69,"line":136},[67,326,95],{"emptyLinePlaceholder":94},[67,328,329,332,335,338,341,344,348,351,354,357,360],{"class":69,"line":144},[67,330,331],{"class":77},"exports.",[67,333,334],{"class":256},"lambdaHandler",[67,336,337],{"class":77}," =",[67,339,340],{"class":245}," async",[67,342,343],{"class":77}," (",[67,345,347],{"class":346},"s7ZW3","event",[67,349,350],{"class":77},",",[67,352,353],{"class":346}," context",[67,355,356],{"class":77},")",[67,358,359],{"class":245}," =>",[67,361,299],{"class":77},[67,363,364,367,370,372],{"class":69,"line":153},[67,365,366],{"class":245},"    let",[67,368,369],{"class":249}," params",[67,371,337],{"class":77},[67,373,299],{"class":77},[67,375,376,379,381,383,386,388],{"class":69,"line":164},[67,377,378],{"class":73},"            TableName",[67,380,78],{"class":77},[67,382,263],{"class":77},[67,384,385],{"class":84},"table",[67,387,263],{"class":77},[67,389,317],{"class":77},[67,391,392],{"class":69,"line":172},[67,393,394],{"class":77},"        };\n",[67,396,397],{"class":69,"line":180},[67,398,95],{"emptyLinePlaceholder":94},[67,400,401,405],{"class":69,"line":188},[67,402,404],{"class":403},"s6cf3","        try",[67,406,407],{"class":77},"{\n",[67,409,410,413,416,418,421,424,427,430,432,435,437,439,442],{"class":69,"line":196},[67,411,412],{"class":245},"            const",[67,414,415],{"class":249}," result",[67,417,337],{"class":77},[67,419,420],{"class":403}," await",[67,422,423],{"class":249}," dynamodb",[67,425,426],{"class":77},".",[67,428,429],{"class":256},"scan",[67,431,260],{"class":73},[67,433,434],{"class":249},"params",[67,436,356],{"class":73},[67,438,426],{"class":77},[67,440,441],{"class":256},"promise",[67,443,444],{"class":73},"()\n",[67,446,447,450],{"class":69,"line":204},[67,448,449],{"class":403},"            return",[67,451,299],{"class":77},[67,453,454,457,459,463],{"class":69,"line":212},[67,455,456],{"class":73},"                statusCode",[67,458,78],{"class":77},[67,460,462],{"class":461},"sx098"," 200",[67,464,317],{"class":77},[67,466,468,471,473,476,478,481,483,486,488,491,493],{"class":69,"line":467},17,[67,469,470],{"class":73},"                body",[67,472,78],{"class":77},[67,474,475],{"class":249}," JSON",[67,477,426],{"class":77},[67,479,480],{"class":256},"stringify",[67,482,260],{"class":73},[67,484,485],{"class":249},"result",[67,487,426],{"class":77},[67,489,490],{"class":249},"Items",[67,492,356],{"class":73},[67,494,317],{"class":77},[67,496,498],{"class":69,"line":497},18,[67,499,500],{"class":77},"            };\n",[67,502,504,507,510,512,515,517],{"class":69,"line":503},19,[67,505,506],{"class":77},"        }",[67,508,509],{"class":403},"catch",[67,511,343],{"class":73},[67,513,514],{"class":249},"e",[67,516,356],{"class":73},[67,518,407],{"class":77},[67,520,522,524],{"class":69,"line":521},20,[67,523,449],{"class":403},[67,525,299],{"class":77},[67,527,529,531,533,536],{"class":69,"line":528},21,[67,530,456],{"class":73},[67,532,78],{"class":77},[67,534,535],{"class":461}," 500",[67,537,317],{"class":77},[67,539,541,543,545,548,550,553],{"class":69,"line":540},22,[67,542,470],{"class":73},[67,544,78],{"class":77},[67,546,547],{"class":249}," e",[67,549,426],{"class":77},[67,551,552],{"class":249},"message",[67,554,317],{"class":77},[67,556,558],{"class":69,"line":557},23,[67,559,500],{"class":77},[67,561,563],{"class":69,"line":562},24,[67,564,565],{"class":77},"        }\n",[67,567,569],{"class":69,"line":568},25,[67,570,571],{"class":77},"}\n",[13,573,574],{},"そしてビルドしてテストをしても",[34,576,579],{"className":577,"code":578,"language":39},[37],"sam build\nsam local invoke -e events\u002Fevent.json\n\nInaccessible host: `127.0.0.1' at port `8000'. This service may not be available in the `ap-north-east1' region.\n",[41,580,578],{"__ignoreMap":43},[13,582,583],{},"とDynamoDBのホストに接続できていないというエラーが発生しました。色々検索した結果、以下の記事がキーとなり解決しました。",[13,585,586],{},[23,587,590],{"href":588,"rel":589},"https:\u002F\u002Ffuture-architect.github.io\u002Farticles\u002F20200323\u002F",[27],"Serverless連載1: SAMを使ったローカルテスト（Go編）",[51,592,593],{"id":593},"解決法",[13,595,596],{},"上記記事の図をみて一気に理解できました。原因はSAMのコンテナがDynamoDBのコンテナに接続できず、またホスト（localhost)の指定が間違っていたからです。",[598,599,601],"h3",{"id":600},"samもdockerで実行されている","SAMもdockerで実行されている",[13,603,604,605],{},"SAMは自動デプロイだけでなく、ローカルでのテストもサポートしています。",[23,606,609],{"href":607,"rel":608},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002Fserverless-application-model\u002Flatest\u002Fdeveloperguide\u002Fserverless-sam-cli-install-linux.html#serverless-sam-cli-install-linux-docker",[27],"テストではDockerを用いることがドキュメントにも書かれています。",[13,611,612,613,616,617,620,621,624],{},"LambdaのコードはDockerのあるコンテナにて実行されることになります。そのため、",[41,614,615],{},"localhost:8000","というのは私のホストOSの8000ポートでなく、SAMが実行されている環境の8000ポートに向いていたのです。SAMコンテナ内の",[41,618,619],{},"locahost:8000","にはDynamoDBなんてありませんから、",[41,622,623],{},"Inaccessible host"," になるのは当たり前です。",[13,626,627],{},"つまりやることはSAMコンテナネットワークと、DynamoDBコンテナネットワークを接続してあげる必要があります。",[598,629,631],{"id":630},"dynamodbの設定とテスト実行時の引数を与える","DynamoDBの設定とテスト実行時の引数を与える",[13,633,634],{},"先ほどのDynamoDBのdocker-compose.yamlを見てみると、ネットワークの設定があります。",[34,636,638],{"className":60,"code":637,"filename":62,"language":63,"meta":43,"style":43},"version: \"3\"\n\nservices:\n  dynamodb-local:\n    container_name: dynamodb-local\n    image: amazon\u002Fdynamodb-local\n    ports:\n      - 8000:8000\n    command: -jar DynamoDBLocal.jar -dbPath \u002Fdata -sharedDb\n    volumes:\n      - .\u002Fdata:\u002Fdata\n    networks:\n      - lambda-local\nnetworks:\n  lambda-local:\n    external: true #これ！\n",[41,639,640,652,656,662,668,676,684,690,696,704,710,716,722,728,734,740],{"__ignoreMap":43},[67,641,642,644,646,648,650],{"class":69,"line":70},[67,643,74],{"class":73},[67,645,78],{"class":77},[67,647,81],{"class":77},[67,649,85],{"class":84},[67,651,88],{"class":77},[67,653,654],{"class":69,"line":91},[67,655,95],{"emptyLinePlaceholder":94},[67,657,658,660],{"class":69,"line":98},[67,659,101],{"class":73},[67,661,104],{"class":77},[67,663,664,666],{"class":69,"line":4},[67,665,109],{"class":73},[67,667,104],{"class":77},[67,669,670,672,674],{"class":69,"line":114},[67,671,117],{"class":73},[67,673,78],{"class":77},[67,675,122],{"class":84},[67,677,678,680,682],{"class":69,"line":125},[67,679,128],{"class":73},[67,681,78],{"class":77},[67,683,133],{"class":84},[67,685,686,688],{"class":69,"line":136},[67,687,139],{"class":73},[67,689,104],{"class":77},[67,691,692,694],{"class":69,"line":144},[67,693,147],{"class":77},[67,695,150],{"class":84},[67,697,698,700,702],{"class":69,"line":153},[67,699,156],{"class":73},[67,701,78],{"class":77},[67,703,161],{"class":84},[67,705,706,708],{"class":69,"line":164},[67,707,167],{"class":73},[67,709,104],{"class":77},[67,711,712,714],{"class":69,"line":172},[67,713,147],{"class":77},[67,715,177],{"class":84},[67,717,718,720],{"class":69,"line":180},[67,719,183],{"class":73},[67,721,104],{"class":77},[67,723,724,726],{"class":69,"line":188},[67,725,147],{"class":77},[67,727,193],{"class":84},[67,729,730,732],{"class":69,"line":196},[67,731,199],{"class":73},[67,733,104],{"class":77},[67,735,736,738],{"class":69,"line":204},[67,737,207],{"class":73},[67,739,104],{"class":77},[67,741,742,744,746,749],{"class":69,"line":212},[67,743,215],{"class":73},[67,745,78],{"class":77},[67,747,748],{"class":220}," true",[67,750,752],{"class":751},"sC9rS"," #これ！\n",[13,754,755],{},"そしてSAMコンテナの立ち上げの際、このネットワークに接続できる引数があります。",[34,757,760],{"className":758,"code":759,"language":39},[37],"sam local invoke -e events\u002Fevent.json --docker-network lambda-local\n",[41,761,759],{"__ignoreMap":43},[13,763,764,767],{},[41,765,766],{},"--docker-network lambda-local"," を指定することで、DynamoDBコンテナのネットワークにアクセスできるようになります。そしてホストも以下のように書き換えます。",[34,769,771],{"className":235,"code":770,"filename":237,"language":238,"meta":43,"style":43},"var dynamoOpt = {\n    endpoint: \"http:\u002F\u002Fdynamodb-local:8000\",\n};\n",[41,772,773,783,798],{"__ignoreMap":43},[67,774,775,777,779,781],{"class":69,"line":70},[67,776,291],{"class":245},[67,778,294],{"class":249},[67,780,253],{"class":77},[67,782,299],{"class":77},[67,784,785,787,789,791,794,796],{"class":69,"line":91},[67,786,304],{"class":73},[67,788,78],{"class":77},[67,790,81],{"class":77},[67,792,793],{"class":84},"http:\u002F\u002Fdynamodb-local:8000",[67,795,314],{"class":77},[67,797,317],{"class":77},[67,799,800],{"class":69,"line":98},[67,801,322],{"class":77},[13,803,804],{},"実際のコードではホスト部分は環境変数に置き換えます。とりあえず上記ホストに変更して、もう一度先ほどのコマンドを実行しました。結果、なんとかDynamoDBに接続してCRUD操作ができるようになりました。",[13,806,807],{},"複数のコンテナを立てていたりすると、どこがどう繋がっているか分からなくなるので結構はまりました。汗）",[809,810,811],"style",{},"html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"title":43,"searchDepth":98,"depth":98,"links":813},[814,815],{"id":53,"depth":91,"text":54},{"id":593,"depth":91,"text":593,"children":816},[817,818],{"id":600,"depth":98,"text":601},{"id":630,"depth":98,"text":631},[820],"ministack","2026-05-07","AWS SAMとDynamoDBの接続","md",null,{},"\u002Farticles\u002Faws-sam-disconnect-dymamodb-local",{"title":8,"description":822},"articles\u002Faws-sam-disconnect-dymamodb-local",[830,831,832],"aws","docker","serverless","util\u002Fsam_squirrel.png","wCqQZ02-pnfwd_-TA9YeBhfjRe0nMADGHPc1dk6e2z0",{"id":836,"title":837,"body":838,"category":1716,"createdAt":1718,"description":1719,"extension":823,"index":824,"meta":1720,"navigation":94,"path":1721,"publish":94,"seo":1722,"series":824,"seriesTitle":824,"stem":1723,"tag":1724,"thumbnail":1726,"updatedAt":1718,"__hash__":1727},"articles\u002Farticles\u002Fs3-clioudfront-website.md","AWS S3を使ってSSL&独自ドメインの静的ブログをホストする",{"type":10,"value":839,"toc":1698},[840,843,862,865,872,875,883,890,894,897,902,910,913,916,919,922,941,944,947,950,953,956,959,1140,1156,1170,1176,1179,1182,1186,1189,1207,1210,1214,1223,1226,1229,1232,1235,1238,1241,1244,1247,1250,1253,1256,1259,1268,1272,1275,1278,1281,1284,1287,1290,1293,1296,1299,1302,1306,1315,1322,1325,1333,1336,1339,1342,1350,1353,1363,1370,1373,1376,1379,1382,1387,1393,1418,1421,1434,1443,1447,1454,1458,1461,1467,1470,1473,1476,1479,1482,1485,1488,1491,1494,1497,1517,1520,1524,1541,1569,1591,1597,1605,1608,1612,1626,1682,1685,1688,1692,1695],[13,841,842],{},"こんにちはjunです。会社で静的書き出ししたコンテンツをs3でホストし、cloudfrontを用いてwebサーバーの様にブログ内容をリクエストできる様な構成を作成しました。せっかくなので自分のブログも同じ様にやってみようと思い、復習兼ねて記事にしようと思います。今回は以下のことを説明します。",[844,845,846,850,853,856,859],"ul",{},[847,848,849],"li",{},"S3の設定",[847,851,852],{},"cloudfrontの設定",[847,854,855],{},"独自ドメインの設定（DNSはお名前ドットコム）",[847,857,858],{},"IAMでのデプロイユーザーの作成",[847,860,861],{},"nuxt generate で転送する方法",[13,863,864],{},"私のこのブログはnuxt content を用いてnuxt generate をして静的書き出したものをサーバに転送しています。今まではレンタルサーバーの一部ディレクトリを使用していましたが、AWSの勉強ややってみたい機能をつけやすくするためにS3へ引っ越しました。それでは早速やっていきましょう。",[866,867,871],"div",{"className":868},[869,870],"alert","alert-danger","\nこの記事が説明している内容は2021年10月時点の情報です。\n",[51,873,874],{"id":874},"参考資料",[13,876,877,878],{},"\b",[23,879,882],{"href":880,"rel":881},"https:\u002F\u002Faws.amazon.com\u002Fjp\u002Fpremiumsupport\u002Fknowledge-center\u002Fcloudfront-serve-static-website#Using_a_website_endpoint_as_the_origin.2C_with_anonymous_.28public.29_access_allowed",[27],"CloudFront を使用して、Amazon S3 でホストされた静的ウェブサイトを公開するにはどうすればよいですか?",[13,884,885],{},[23,886,889],{"href":887,"rel":888},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002FRoute53\u002Flatest\u002FDeveloperGuide\u002Frouting-to-cloudfront-distribution.html",[27],"ドメイン名を使用したトラフィックの Amazon CloudFront ディストリビューションへのルーティング",[51,891,893],{"id":892},"s3でデプロイ用のバケットを作成する","S3でデプロイ用のバケットを作成する。",[13,895,896],{},"では最初にまずホスティング元であるバケットを作成します。",[898,899],"image-render",{":src":900,":width":901},"'s3-clioudfront-website\u002Fsch-iam-4.png'","'100%'",[13,903,904,905,909],{},"バケットの名前は ",[906,907,908],"strong",{},"ホストするドメインの名前と同じ"," になるようにします。私の場合はバケット名が「jun-app.com」です。",[898,911],{":src":912,":width":901},"'s3-clioudfront-website\u002Fsch-s3-1.png'",[13,914,915],{},"設定した後にバケット一覧から選択し、プロパティを選びます。プロパティの一番下までスクロールすると「静的ウェブサイトホスティング」とあるのでここを編集します。",[898,917],{":src":918,":width":901},"'s3-clioudfront-website\u002Fsch-s3-2.png'",[898,920],{":src":921,":width":901},"'s3-clioudfront-website\u002Fsch-s3-3.png'",[13,923,924,925,928,929,932,933,936,937,940],{},"無効から有効に変更し、インデックスドキュメントに",[41,926,927],{},"index.html","を入力します。こうすると",[41,930,931],{},"https:\u002F\u002Fjun-app.com\u002Fdir\u002F","みたいにパスだけであっても",[41,934,935],{},"https:\u002F\u002Fjun-app.com\u002Fdir\u002Findex.html","に接続してくれます。また、エラードキュメントを指定しておくことで404リクエストの際のフォールバックとして利用できます。私の場合はルート直下の ",[41,938,939],{},"404.html","を指定しています。",[898,942],{":src":943,":width":901},"'s3-clioudfront-website\u002Fsch-s3-4.png'",[13,945,946],{},"編集後もういちど静的ウェブサイトホスティングの設定までいくと、一応URLが提供されますがアクセスしても「403 Forbidden」となります。これはバケットに対してパブリックアクセスがないからです。バケット作成時、デフォルトでは全てのパブリックアクセスが制限されていたので、その制限を外してあげます。バケットの画面から「アクセス許可」を選択し、「ブロックパブリックアクセス (バケット設定)」の編集をします。（ここを見ると「パブリックアクセスをすべて ブロック」が有効になっている）",[898,948],{":src":949,":width":901},"'s3-clioudfront-website\u002Fsch-s3-5.png'",[13,951,952],{},"編集の画面で「パブリックアクセスをすべて ブロック」のチェックを外して、全てのブロックを外します。",[898,954],{":src":955,":width":901},"'s3-clioudfront-website\u002Fsch-s3-6.png'",[13,957,958],{},"まだ終わりではありません。先ほどのはバケット単位の設定であり、アップロードされるオブジェクト（静的ファイル）には公開される設定がされません。毎回オブジェクトに対してパブリックアクセスを与えるのは面倒なので、「アクセス許可」の画面にあるバケットポリシーを以下のように編集します。",[34,960,964],{"className":961,"code":962,"language":963,"meta":43,"style":43},"language-JSON shiki shiki-themes material-theme-ocean","{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"PublicReadGetObject\",\n            \"Effect\": \"Allow\",\n            \"Principal\": \"*\",\n            \"Action\": [\n                \"s3:GetObject\"\n            ],\n            \"Resource\": [\n                \"arn:aws:s3:::your-buget-name\u002F*\"\n            ]\n        }\n    ]\n}\n","JSON",[41,965,966,970,991,1005,1010,1032,1052,1072,1085,1095,1100,1113,1122,1127,1131,1136],{"__ignoreMap":43},[67,967,968],{"class":69,"line":70},[67,969,407],{"class":77},[67,971,972,975,978,980,982,984,987,989],{"class":69,"line":91},[67,973,974],{"class":77},"    \"",[67,976,977],{"class":245},"Version",[67,979,314],{"class":77},[67,981,78],{"class":77},[67,983,81],{"class":77},[67,985,986],{"class":84},"2012-10-17",[67,988,314],{"class":77},[67,990,317],{"class":77},[67,992,993,995,998,1000,1002],{"class":69,"line":98},[67,994,974],{"class":77},[67,996,997],{"class":245},"Statement",[67,999,314],{"class":77},[67,1001,78],{"class":77},[67,1003,1004],{"class":77}," [\n",[67,1006,1007],{"class":69,"line":4},[67,1008,1009],{"class":77},"        {\n",[67,1011,1012,1015,1019,1021,1023,1025,1028,1030],{"class":69,"line":114},[67,1013,1014],{"class":77},"            \"",[67,1016,1018],{"class":1017},"s5Dmg","Sid",[67,1020,314],{"class":77},[67,1022,78],{"class":77},[67,1024,81],{"class":77},[67,1026,1027],{"class":84},"PublicReadGetObject",[67,1029,314],{"class":77},[67,1031,317],{"class":77},[67,1033,1034,1036,1039,1041,1043,1045,1048,1050],{"class":69,"line":125},[67,1035,1014],{"class":77},[67,1037,1038],{"class":1017},"Effect",[67,1040,314],{"class":77},[67,1042,78],{"class":77},[67,1044,81],{"class":77},[67,1046,1047],{"class":84},"Allow",[67,1049,314],{"class":77},[67,1051,317],{"class":77},[67,1053,1054,1056,1059,1061,1063,1065,1068,1070],{"class":69,"line":136},[67,1055,1014],{"class":77},[67,1057,1058],{"class":1017},"Principal",[67,1060,314],{"class":77},[67,1062,78],{"class":77},[67,1064,81],{"class":77},[67,1066,1067],{"class":84},"*",[67,1069,314],{"class":77},[67,1071,317],{"class":77},[67,1073,1074,1076,1079,1081,1083],{"class":69,"line":144},[67,1075,1014],{"class":77},[67,1077,1078],{"class":1017},"Action",[67,1080,314],{"class":77},[67,1082,78],{"class":77},[67,1084,1004],{"class":77},[67,1086,1087,1090,1093],{"class":69,"line":153},[67,1088,1089],{"class":77},"                \"",[67,1091,1092],{"class":84},"s3:GetObject",[67,1094,88],{"class":77},[67,1096,1097],{"class":69,"line":164},[67,1098,1099],{"class":77},"            ],\n",[67,1101,1102,1104,1107,1109,1111],{"class":69,"line":172},[67,1103,1014],{"class":77},[67,1105,1106],{"class":1017},"Resource",[67,1108,314],{"class":77},[67,1110,78],{"class":77},[67,1112,1004],{"class":77},[67,1114,1115,1117,1120],{"class":69,"line":180},[67,1116,1089],{"class":77},[67,1118,1119],{"class":84},"arn:aws:s3:::your-buget-name\u002F*",[67,1121,88],{"class":77},[67,1123,1124],{"class":69,"line":188},[67,1125,1126],{"class":77},"            ]\n",[67,1128,1129],{"class":69,"line":196},[67,1130,565],{"class":77},[67,1132,1133],{"class":69,"line":204},[67,1134,1135],{"class":77},"    ]\n",[67,1137,1138],{"class":69,"line":212},[67,1139,571],{"class":77},[13,1141,1142,1143,1145,1146,1148,1149,1151,1152,1155],{},"上記のJSONは",[41,1144,1058],{},"はこのポリシーを付与するユーザーです。",[41,1147,1067],{},"としてゲストを含む全てのユーザーに",[41,1150,1092],{},"のアクション、つまりファイルの読み取りを",[41,1153,1154],{},"\"arn:aws:s3:::your-buget-name\u002F*\"","のバケットオブジェクトに許可するという意味です。",[13,1157,1158,1161,1162,1165,1166,1169],{},[41,1159,1160],{},"your-buget-name","にはあなたが設定したバケット名を入力します。そしてそのバケット配下の全オブジェクトに適用するため",[41,1163,1164],{},"your-buget-name\u002F*","とディレクトリのように指定します。",[41,1167,1168],{},"\"arn:aws:s3:::your-buget-name","という記述（Amazon リソースネーム）はバケットのプロパティで取得できます。",[13,1171,1172,1173,1175],{},"こうすればこのバケットないのファイルは公開されます。試しに",[41,1174,927],{},"をアップロードして、提供されたオブジェクト URLにアクセスすると確かに見れます。",[898,1177],{":src":1178,":width":901},"'s3-clioudfront-website\u002Fsch-s3-7.png'",[13,1180,1181],{},"これでS3側の設定が完了しました。ただしドメインはS3の初期状態のままです。独自ドメインを割り当てとSSL化を行います。",[51,1183,1185],{"id":1184},"sslと独自ドメインを割り当てる","SSLと独自ドメインを割り当てる",[13,1187,1188],{},"SSL化した独自ドメインをS3に接続させるためにはcloudfrontの力が必要です。また今回使用するドメイン「jun-app.com」はお名前ドットコムで取得しているので、CNAMEを加えるなどの設定が必要です。クライアントがコンテンツを取得する流れは以下のようになります。",[1190,1191,1192,1195,1198,1201,1204],"ol",{},[847,1193,1194],{},"クライアントがURLにアクセス",[847,1196,1197],{},"お名前ドットコムへDNS問い合わせ",[847,1199,1200],{},"お名前ドットコムはRoute53にNSを移譲しているので、問い合わせはRoute53へ",[847,1202,1203],{},"Route53 はcloudfrontのIPを返す",[847,1205,1206],{},"cloudfrontからS3に接続",[13,1208,1209],{},"といった流れです。",[598,1211,1213],{"id":1212},"route53と外部dnsの設定","Route53と外部DNSの設定",[13,1215,1216,1217,1222],{},"まずはRoute53でホストゾーンというものを作成して、お名前.comからNSサーバーをRoute53へ委任できるようにします。",[23,1218,1221],{"href":1219,"rel":1220},"https:\u002F\u002Fconsole.aws.amazon.com\u002Froute53\u002Fv2\u002Fhome#Dashboard",[27],"Route53","に移動して「ホストゾーンの作成」をします。",[898,1224],{":src":1225,":width":901},"'s3-clioudfront-website\u002Fsch-r53-1.png'",[13,1227,1228],{},"委任したいドメインを入力します。「パブリックホストゾーン」で大丈夫です。タグは管理上のものなので空欄で大丈夫です。問題なければ「ホストゾーンの作成」をクリックします。",[898,1230],{":src":1231,":width":901},"'s3-clioudfront-website\u002Fsch-r53-2.png'",[13,1233,1234],{},"作成後にはこのようにNSレコードとSOAレコードが作成されます。ドメイン自体が外部DNS（お名前.com）にある場合この「NSレコード」を指定することでRoute53に委任します。（ぼやけていますが４つ作成されます。）",[898,1236],{":src":1237,":width":901},"'s3-clioudfront-website\u002Fsch-r53-3.png'",[13,1239,1240],{},"このNSレコードをお名前.comの場合は「ネームサーバーの設定」を選択します。",[898,1242],{":src":1243,":width":901},"'s3-clioudfront-website\u002Fsch-r53-4.png'",[13,1245,1246],{},"そして「2.ネームサーバーの選択」＞「その他」＞「その他のネームサーバーを使う」にてRoute53で表示された４つのNSレコードをいれて「確認」クリックします。",[898,1248],{":src":1249,":width":901},"'s3-clioudfront-website\u002Fsch-r53-5.png'",[13,1251,1252],{},"「確認」の後にはDNSのNSが切り替わり、「jun-app.com」はまずお名前.comへ問い合わせられますが、結局Route53へたらい回しされます。これで「jun-app.com」のAレコードなどをRoute53で制御できるようになりました。",[13,1254,1255],{},"次はS3とcloudfrontと連携させてSSLを利用できるようにします。",[598,1257,1258],{"id":1258},"独自ドメインの証明書を取得する",[13,1260,1261,1262,1267],{},"まずcloudfrontの設定の前に独自ドメイン（jun-app.com）のSSL証明書をAWSで取得しておきます。AWSでは",[23,1263,1266],{"href":1264,"rel":1265},"https:\u002F\u002Fap-northeast-1.console.aws.amazon.com\u002Facm\u002Fhome",[27],"AWS Certificate Manager"," で取得できます。ただしここで注意点があります。",[866,1269,1271],{"className":1270},[869,870],"\n以下の画像のようにAWS Certificate Managerでのカスタム証明書(独自ドメインの証明書)をcloudfrontに割り当てる場合、AWS Certificate Managerのリージョンが「米国東部 (バージニア北部) リージョン (us-east-1) 」でないといけません。私は間違って東京リージョンで作成してしまい、証明書がサジェストで出てこずはまりました。\n",[898,1273],{":src":1274,":width":901},"'s3-clioudfront-website\u002Fsch-cfm-1.png'",[13,1276,1277],{},"リージョンを「us-east-1」に変更したのち「証明書をリクエスト」をクリックして作成します。",[898,1279],{":src":1280,":width":901},"'s3-clioudfront-website\u002Fsch-cfm-2.png'",[13,1282,1283],{},"証明書のタイプは「パブリック証明書をリクエスト」を選択し、ドメイン名などを入力します。そして検証では「DNS検証」を行います。Route53で委任してあれば「DNS検証」ですぐに発行できます。DNS検証はドメインを管理しているDNSにAWS Certificate Managerで発行されたCNAMEを設定することで、ドメインの所有権を確認します。全て入力して「リクエスト」となります。",[898,1285],{":src":1286,":width":901},"'s3-clioudfront-website\u002Fsch-cfm-3.png'",[13,1288,1289],{},"リクエスト後には一覧に入力したドメインが現れ、対応するCNAMEが現れます。Route53で管理している場合は「Route53でレコードを作成」のボタンを押すだけで作ってくれます。手動で設定する場合は「CNAMEレコード」を作成して、その値を設定するだけです。",[898,1291],{":src":1292,":width":901},"'s3-clioudfront-website\u002Fsch-cfm-4.png'",[13,1294,1295],{},"このようにDNS検証に必要なCNAMEが追加されています。",[898,1297],{":src":1298,":width":901},"'s3-clioudfront-website\u002Fsch-cfm-5.png'",[13,1300,1301],{},"数分経つと証明書の検証が完了となりますので、これで「jun-app.com」の証明書の準備ができました。次はcloudfrontとS3を独自ドメインを用いて連携します。",[598,1303,1305],{"id":1304},"cloudfrontとs3を連携","cloudfrontとS3を連携",[13,1307,1308,1309,1314],{},"それではcloudfrontとS3を独自ドメインを連携します。",[23,1310,1313],{"href":1311,"rel":1312},"https:\u002F\u002Fconsole.aws.amazon.com\u002Fcloudfront\u002Fv3\u002Fhome?#\u002F",[27],"cloudfront","のコンソールに移動して「ディスりビーションの作成」をクリックします。",[13,1316,1317,1318,1321],{},"そしてcloudfrontと連携するAWSリソースやオリジンなどを設定します。「オリジンドメイン」でS3リソースを選択するのですがS3をwebサーバーとして利用する場合 ",[906,1319,1320],{},"サジェストに出てくる 「~~~~.s3.ap-northeast-1.amazonaws.com」 を選択してはいけません。"," これは単純に「S3オブジェクトURL」でありwebサーバーのような動きをしません。",[898,1323],{":src":1324,":width":901},"'s3-clioudfront-website\u002Fsch-clf-1.png'",[13,1326,1327,1328],{},"今回の構成ではS3の「静的ウェブサイトホスティング」を有効にした際に出現した（下図のボケているとこ）のバケットウェブサイトエンドポイントをいれます。このバケットウェブサイトエンドポイントは「S3をwebサーバーとして使用するときのドメイン」です。ここを忘れて「S3オブジェクトURL」に設定しても一応ルートはs3に接続はできるのですが、「",[23,1329,1332],{"href":1330,"rel":1331},"https:\u002F\u002Fjun-app.com\u002Ftest\u002F%E3%80%8D%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E6%8E%A5%E7%B6%9A%E3%81%99%E3%82%8B%E3%81%A8%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%8C%E7%99%BA%E7%94%9F%E3%81%97%E3%81%9F%E3%82%8A%E3%80%81%E6%80%9D%E3%81%86%E3%82%88%E3%81%86%E3%81%AB%E5%8B%95%E3%81%8D%E3%81%BE%E3%81%9B%E3%82%93%E3%80%82%E7%B5%90%E6%A7%8B%E3%83%8F%E3%83%9E%E3%82%8A%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%81%A7%E3%81%99%E3%80%82",[27],"https:\u002F\u002Fjun-app.com\u002Ftest\u002F」のように接続するとエラーが発生したり、思うように動きません。結構ハマりポイントです。",[898,1334],{":src":1335,":width":901},"'s3-clioudfront-website\u002Fsch-clf-2.png'",[13,1337,1338],{},"オリジンの設定をしたら他の項目は基本的にデフォルトのままでOKです。下に進んで独自ドメインと証明書を設定する箇所があります。「代替ドメイン名 (CNAME)」に連携したいドメイン名を入力し、そのドメインに対応する証明書（先ほど取得した証明書）を選択します。",[898,1340],{":src":1341,":width":901},"'s3-clioudfront-website\u002Fsch-clf-3.png'",[13,1343,1344,1345],{},"最後にデフォルトルートオブジェクトに「index.html」を設定します。こうすると「",[23,1346,1349],{"href":1347,"rel":1348},"https:\u002F\u002Fjun-app.com\u002Ftest\u002F%E3%80%8D%E3%81%A8%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%97%E3%81%9F%E9%9A%9B%E3%80%8Chttps:\u002F\u002Fjun-app.com\u002Ftest\u002Findex.html%E3%80%8D%E3%81%8C%E8%87%AA%E5%8B%95%E7%9A%84%E3%81%AB%E8%BF%94%E3%81%95%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82",[27],"https:\u002F\u002Fjun-app.com\u002Ftest\u002F」とリクエストした際「https:\u002F\u002Fjun-app.com\u002Ftest\u002Findex.html」が自動的に返されます。",[898,1351],{":src":1352,":width":901},"'s3-clioudfront-website\u002Fsch-clf-4.png'",[13,1354,1355,1356,1359,1360,1362],{},"これで一通りcloudfrontとS3の連携が完了しました。「ディストリビーションを作成」を行いますと一覧にて ",[41,1357,1358],{},"~~~~~~.cloudfront.net"," というS3オリジンに対するcloudfrontのドメインが作成されます。このドメインにアクセスしてみますと、SSL付きでS3上にアップロードした ",[41,1361,927],{}," が表示されます。",[13,1364,1365,1366,1369],{},"つぎは cloudfrontのドメインが ",[41,1367,1368],{},"jun-app.com"," に向くようにRoute53を設定します。",[598,1371,1372],{"id":1372},"cloudfrontのドメインを独自ドメインに向ける",[13,1374,1375],{},"ではRoute53に戻ってcloudfrontのドメインが独自ドメインに向くようにします。ドメインのホストゾーンにて「レコードの作成」を選択します。そして以下のような画面が開きます。「レコード名」はサブドメインを作るわけでないので、空欄で大丈夫です。そしてレコードタイプは「A - ipvアドレスと..」を選択します。「トラフィックのルーティング先」のエイリアスをONにします。（トグルになっています）",[13,1377,1378],{},"Aレコードは普通、IPアドレスを入力しますがcloudfrontなどのAWSリソースでしているする場合「エイリアス」を使用します。エイリアスでは「CloudFront ディストリビューションへのエイリアス」を選択します。そしてその下にcloudfrontのディストリビューションの入力欄が現れますので、そこに先ほどのcloudfrontのドメインを入力します。",[898,1380],{":src":1381,":width":901},"'s3-clioudfront-website\u002Fsch-clf-r53-1.png'",[866,1383,1386],{"className":1384},[869,1385],"alert-info","\n本来であれば登録したcloudfrontのディストリビューションがサジェストに表示されますが、登録したてだと出てこないことがあります。ただディストリビューションがあればとりあえず登録できます。\n",[13,1388,1389,1390,1392],{},"そして最後に「レコードの作成」をクリックします。すると ",[41,1391,1368],{}," に対するAレコードがcloudfrontのディストリビューションに向くようになります。ここまでドメインの流れを追うと以下の通りになります。",[1190,1394,1395,1400,1413],{},[847,1396,1397,1399],{},[41,1398,1368],{}," にリクエストしたとき、まずお名前.comに問い合わせるが「Route53」に委任されているのでRoute53へ問い合わせ",[847,1401,1402,1403,1405,1406,1409,1410,1412],{},"Route53では ",[41,1404,1368],{}," のAレコードが ",[41,1407,1408],{},"xxxx.cloudfront.net"," に向いているので、",[41,1411,1408],{},"へ接続。",[847,1414,1415,1417],{},[41,1416,1408],{}," は S3のwebホスティングURLに向いているので、そこに接続",[13,1419,1420],{},"となります。（結構ざっくりとしてます）\n以上でバックエンド側の準備ができました。すでにドメインを使用している場合はDNSキャッシュが効いていることがあります。別の端末からアクセスしたり、DNSキャッシュをクリアするコマンドを打ってみましょう。",[866,1422,1425,1426,1429,1430,1433],{"className":1423},[869,1424],"alert-success","\nDNSの設定がうまく、対象のサーバーに向いているかを調べるときは",[41,1427,1428],{},"nslookup","や",[41,1431,1432],{},"dig","コマンドを使って対象ドメインに紐づけられているサーバーを確かめてみましょう。\n",[13,1435,1436,1437,1439,1440,1442],{},"現在のところS3に",[41,1438,927],{},"が置いてあれば、独自ドメイン＋SSLでその",[41,1441,927],{},"が表示されていればOKです。",[51,1444,1446],{"id":1445},"aws-cliを用いたs3へのファイルのデプロイ","AWS CLIを用いたS3へのファイルのデプロイ",[13,1448,1449,1450,1453],{},"では最後にブログファイル群をS3にアップロードします。私の場合はNuxt.jsであらかじめ書き出しを行い、",[41,1451,1452],{},"dist","ファイル配下を送信します。手動は毎回大変ですのでAWS CLIを用いて自動化と差分アップロード できるようにします。その手順を解説します。最初にAWS CLIをもちいて対象バケットにアクセスできるIAMユーザーを作成します。",[598,1455,1457],{"id":1456},"iamでデプロイユーザーを作成する","IAMでデプロイユーザーを作成する",[13,1459,1460],{},"AWSの運用ベストプラクティスでは",[1462,1463,1464],"blockquote",{},[13,1465,1466],{},"AWS アカウントのルートユーザー のアクセスキーを使用しないでください。\nIAM により、複数のユーザーに AWS リソースへの安全なアクセスを簡単に提供できます。IAM により以下が可能となります。",[13,1468,1469],{},"とある様に基本的にIAMというものを用いてリソースにアクセスする様にします。例えば今回の様にAWS CLIを用いて自分のAWSリソースにアクセスする時にシークレットキーとアクセスキーを使用します。AWSを始めた時に作ったRootアカウントでも可能ですが、rootはCLIを通じで本当になんでもできてしまうので使うべきではありません。であればアクションとアクセスを制限されたユーザー（IAM）を使用すれば、もしキーが漏れたとしても被害は小さめです。なので今回のブログデプロイ用のIAMを作成してしまいます。",[13,1471,1472],{},"AWSにログインしてIAMに移動し、「ユーザーを追加」をクリックします。",[898,1474],{":src":1475,":width":901},"'s3-clioudfront-website\u002Fsch-iam-0.png'",[13,1477,1478],{},"ユーザー名を設定し、「プログラムによるアクセス」にチェックをし、次のアクセス権限の設定を行います。",[898,1480],{":src":1481,":width":901},"'s3-clioudfront-website\u002Fsch-iam-1.png'",[13,1483,1484],{},"ここでこのユーザーがアクセスできるアクションを設定できます。一応JSONを用いて細かい設定もできますが、今回はチュートリアル的な内容なので「AmazonS3FullAccess」を与えておきます。",[898,1486],{":src":1487,":width":901},"'s3-clioudfront-website\u002Fsch-iam-2.png'",[13,1489,1490],{},"次にタグを設定しますが、組織でなければ特に設定しません。最後に全体を確認して「ユーザーを作成」をします。これでS3だけにアクセスできるIAMができます。",[898,1492],{":src":1493,":width":901},"'s3-clioudfront-website\u002Fsch-iam-3.png'",[13,1495,1496],{},"このユーザー用のアクセスキーとシークレットキーがダウンロードできますので、控えておきます。まずデプロイ用のIAMユーザーができました。AWS CLIのインストールはここでは割愛しまが、ここで取得したIAMのキーをプロファイルに設定します。",[34,1498,1502],{"className":1499,"code":1500,"language":1501,"meta":43,"style":43},"language-bash shiki shiki-themes material-theme-ocean","aws configure --profile junapp-s3\n","bash",[41,1503,1504],{"__ignoreMap":43},[67,1505,1506,1508,1511,1514],{"class":69,"line":70},[67,1507,830],{"class":1017},[67,1509,1510],{"class":84}," configure",[67,1512,1513],{"class":84}," --profile",[67,1515,1516],{"class":84}," junapp-s3\n",[13,1518,1519],{},"プロファイル名は分かりやすい名前にしておくといいです。これでIAMと転送のセットアップは完了です。",[598,1521,1523],{"id":1522},"ファイルを生成してs3へアップロード","ファイルを生成してS3へアップロード",[13,1525,1526,1527,1530,1531,1533,1534,1536,1537,1540],{},"私の環境では ",[41,1528,1529],{},"npm run generate"," で ",[41,1532,1452],{}," ファイルが生成され、必要なHTMLファイルとアセットが出力されます。その ",[41,1535,1452],{},"ファイル配下を全て",[41,1538,1539],{},"s3:\u002F\u002Fjun-app.com\u002F","へ転送します。そのときはAWS CLIで以下のコマンドを使用します。",[34,1542,1544],{"className":1499,"code":1543,"language":1501,"meta":43,"style":43},"aws s3 sync .\u002Fdist\u002F s3:\u002F\u002Fjun-app.com\u002F --delete --profile junapp-s3\n",[41,1545,1546],{"__ignoreMap":43},[67,1547,1548,1550,1553,1556,1559,1562,1565,1567],{"class":69,"line":70},[67,1549,830],{"class":1017},[67,1551,1552],{"class":84}," s3",[67,1554,1555],{"class":84}," sync",[67,1557,1558],{"class":84}," .\u002Fdist\u002F",[67,1560,1561],{"class":84}," s3:\u002F\u002Fjun-app.com\u002F",[67,1563,1564],{"class":84}," --delete",[67,1566,1513],{"class":84},[67,1568,1516],{"class":84},[13,1570,1571,1574,1575,1578,1579,1582,1583,1586,1587,1590],{},[41,1572,1573],{},"s3 sync","は2回目以降、差分をみてあれば同期してくれます。二回目以降は転送量を節約できます。",[41,1576,1577],{},"--delete"," は同期先（S3）にて同期元（ローカル）にないファイルを消してくれます。例えばnuxt.jsではキャッシュ対策のため、",[41,1580,1581],{},"_nuxt","ディレクトリ配下に",[41,1584,1585],{},"34234324.js","みたいなハッシュ化したjsファイルが書き出しごとに生成されます。単純に転送していくと",[41,1588,1589],{},"__nuxt","配下にjsファイルが溜まっていくので差分を見て古いファイルを削除します。他にも「メニューなどにはないけれど、削除した記事がファイルとしては残ったままになっている」みたいな事故も防げます。",[13,1592,1593,1596],{},[41,1594,1595],{},"--profile"," にて先ほど設定したIAMのプロファイルを指定します。",[866,1598,1600,1601,1604],{"className":1599},[869,1385],"\nこのコマンドを実行する前に ",[41,1602,1603],{},"--dryrun","のオプションをいれてシミュレートしてから本番を実行しましょう。\n",[13,1606,1607],{},"準備ができたらコマンドを打ちます。終わったらバケットを確認して終了です。",[1609,1610,1611],"h4",{"id":1611},"スクリプトに入れておくと便利",[13,1613,1614,1615,1617,1618,1621,1622,1625],{},"Nuxt.jsで",[41,1616,1529],{},"した際に",[41,1619,1620],{},"sync","されるように",[41,1623,1624],{},"package.json","を以下のように編集しておきました。",[34,1627,1632],{"className":1628,"code":1629,"filename":1630,"language":1631,"meta":43,"style":43},"language-json shiki shiki-themes material-theme-ocean","{\n\"scripts\": {\n    ...\n    \"generate-production\": \"nuxt generate && aws s3 sync .\u002Fdist\u002F s3:\u002F\u002Fjun-app.com\u002F --delete --profile junapp-s3\"\n}\n}\n","pakcage.json","json",[41,1633,1634,1638,1651,1656,1674,1678],{"__ignoreMap":43},[67,1635,1636],{"class":69,"line":70},[67,1637,407],{"class":77},[67,1639,1640,1642,1645,1647,1649],{"class":69,"line":91},[67,1641,314],{"class":77},[67,1643,1644],{"class":245},"scripts",[67,1646,314],{"class":77},[67,1648,78],{"class":77},[67,1650,299],{"class":77},[67,1652,1653],{"class":69,"line":98},[67,1654,1655],{"class":249},"    ...\n",[67,1657,1658,1660,1663,1665,1667,1669,1672],{"class":69,"line":4},[67,1659,974],{"class":77},[67,1661,1662],{"class":1017},"generate-production",[67,1664,314],{"class":77},[67,1666,78],{"class":77},[67,1668,81],{"class":77},[67,1670,1671],{"class":84},"nuxt generate && aws s3 sync .\u002Fdist\u002F s3:\u002F\u002Fjun-app.com\u002F --delete --profile junapp-s3",[67,1673,88],{"class":77},[67,1675,1676],{"class":69,"line":114},[67,1677,571],{"class":77},[67,1679,1680],{"class":69,"line":125},[67,1681,571],{"class":77},[1609,1683,1684],{"id":1684},"cloudfrontのキャッシュに注意",[13,1686,1687],{},"cloudfrontはCDNであり、キャッシュが強いです。更新したのに本番が変わらないときはキャッシュのせいかもしれません。cloudfrontでは明示的にキャッシュをクリアすることもできますし、キャッシュの期間を変更することもできます。閲覧数などに合わせて設定しましょう。",[51,1689,1691],{"id":1690},"以上","以上！",[13,1693,1694],{},"以上で S3 x SSL x 独自ドメインな静的ブログを構築できました。利用量によっては無料枠でも収まりそうです。しばらく料金の方も見てみてレンサバよりお得かを確認してみます。",[809,1696,1697],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}",{"title":43,"searchDepth":98,"depth":98,"links":1699},[1700,1701,1702,1708,1715],{"id":874,"depth":91,"text":874},{"id":892,"depth":91,"text":893},{"id":1184,"depth":91,"text":1185,"children":1703},[1704,1705,1706,1707],{"id":1212,"depth":98,"text":1213},{"id":1258,"depth":98,"text":1258},{"id":1304,"depth":98,"text":1305},{"id":1372,"depth":98,"text":1372},{"id":1445,"depth":91,"text":1446,"children":1709},[1710,1711],{"id":1456,"depth":98,"text":1457},{"id":1522,"depth":98,"text":1523,"children":1712},[1713,1714],{"id":1611,"depth":4,"text":1611},{"id":1684,"depth":4,"text":1684},{"id":1690,"depth":91,"text":1691},[1717],"devstack","2025-07-18","AWS S3とcloudfrontを合わせることでwebサーバの様に扱う方法",{},"\u002Farticles\u002Fs3-clioudfront-website",{"title":837,"description":1719},"articles\u002Fs3-clioudfront-website",[1725,830],"infrastructure","s3-clioudfront-website\u002Fthumbnail.png","TS4Cse0E-KmfWg0S6g6WJdYqcKJXhparmDnjSsmNt20",{"id":1729,"title":1730,"body":1731,"category":1833,"createdAt":1834,"description":1835,"extension":823,"index":824,"meta":1836,"navigation":94,"path":1837,"publish":94,"seo":1838,"series":824,"seriesTitle":824,"stem":1839,"tag":1840,"thumbnail":1841,"updatedAt":824,"__hash__":1842},"articles\u002Farticles\u002Fhandle-cloudfront-cache.md","cloudfrontでクエリパラメータを使ってコンテンツの更新におけるキャッシュを制御する",{"type":10,"value":1732,"toc":1831},[1733,1746,1749,1756,1764,1771,1774,1777,1782,1785,1788,1791,1794,1797,1800,1803,1806,1817,1820],[13,1734,1735,1736,1741,1742,1745],{},"こんにちはjunです。",[23,1737,1740],{"href":1738,"rel":1739},"https:\u002F\u002Froute-share.net",[27],"RouteShareというユーザー投稿型の個人開発","でユーザーがアップロードしたファイルをS3に配置し、cloudfrontでキャッシュを効かせて取得するように設定しました。このときユーザーアバターや投稿コンテンツの自動生成サムネイルなど特定のファイルは",[41,1743,1744],{},"{ID}.png","のようにDB上のIDと拡張子で表現していました。IDベースのファイル名にすることでIDさえわかればユーザーや投稿のサムネイルが表示できるというメリットがありました。",[13,1747,1748],{},"しかし上記の方法ではファイル名が変わらず、更新した際のcloudfrontのキャッシュで画像が切り替わらないという事態がありました。今回はこのような「cloudfront上で同じ名前で管理しているが、都度更新があった際に確実に更新したファイルを表示させる方法」についての内容です。",[13,1750,1751],{},[23,1752,1755],{"href":1753,"rel":1754},"https:\u002F\u002Faws.amazon.com\u002Fjp\u002Fpremiumsupport\u002Fknowledge-center\u002Fcloudfront-serving-outdated-content-s3\u002F",[27],"参考記事はこちら",[13,1757,1758,1759],{},"まず最初にcloudfrontにはサービス上でキャッシュを削除する機能はあります。指定のパスを設定することで、キャッシュさせたURL（ここではファイルURL）をcloudfront上から削除することができます。キャッシュがなくなるのでオリジンに取得しにいき、更新したコンテンツを取得できます。しかしそれをコンテンツごとに行うのは大変です。単純に数も多いですし更新があったコンテンツを検知して、APIでcloudfrontの操作を行う必要があるからです。",[23,1760,1763],{"href":1761,"rel":1762},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002FAmazonCloudFront\u002Flatest\u002FDeveloperGuide\u002FInvalidation.html#PayingForInvalidation",[27],"また無料枠分はありますが、キャッシュの削除にはお金がかかります。",[13,1765,1766,1767,1770],{},"cloudfrontは実はキャッシュは効かさないこともできますが、折角のCDNが勿体無いです。更新を効かしつつも普段はキャッシュさせてパフォーマンスを上げる方法としては、ファイル名に",[41,1768,1769],{},"?ver=xxxx","のようなクエリパラメータをつけて、別のURLとして認識させる方法があります。これはcloudfrontのみならず、他のキャッシュ対策でもよく行われます。バージョンパラメータはクライアント側で制御します。例えば、ユーザーアバターの際はアップロード更新時にユーザーレコードのupdated_atを更新し、ユーザーレコードを参照する箇所ではそのupdated_atをクエリパラメータに入れます。ビルドしたjsやcssなどにはビルド日時とかを入れられるようにするといいかもしれません。",[13,1772,1773],{},"ただし、cloudfrontのデフォルトのキャッシュポリシーであるCachingOptimizedはこのクエリパラメータを考慮しません。どんなクエリパラメータをつけようが、同じファイル名であればキャッシュしてしまいます。そのため、新しいキャッシュポリシーを加えます。",[13,1775,1776],{},"最初にcloudfrontの画面を開き、ポリシーを選択します。",[898,1778],{":src":1779,":width":1780,":center":1781,":current":1781},"'menu.png'","'300px'","true",[13,1783,1784],{},"カスタムポリシーからキャッシュポリシーを作成を選択",[898,1786],{":src":1787,":center":1781,":current":1781},"'create.png'",[13,1789,1790],{},"クエリパラメータを有効にしたい既存のキャッシュポリシーと同じにします。今回はデフォルトのCachingOptimizedとTTLや圧縮サポートを選択。そして 「キャッシュキー設定」にてクエリ文字列を追加し、扱うパラメータを入力します。ポリシー名を設定し、問題なければ作成をクリックしてポリシーを登録します。",[898,1792],{":src":1793,":center":1781,":current":1781},"'cache_key.png'",[13,1795,1796],{},"次にポリシーを当てはめたいディストリビューションのビヘイビアを編集します。",[898,1798],{":src":1799,":center":1781,":current":1781},"'behavior.png'",[13,1801,1802],{},"キャッシュポリシーを先ほどの名前のものに設定します。",[898,1804],{":src":1805,":center":1781,":current":1781},"'set_policy.png'",[13,1807,1808,1809,1812,1813,1816],{},"変更を保存をクリックして完了です。こうすれば",[41,1810,1811],{},"?ver=2022-12-01-00-00-01","から",[41,1814,1815],{},"?ver=2022-12-01-00-00-02","に変更した際に別のリソースとして認識され、オリジンへの取得が行われ再度そのクエリパラメータと共にキャッシュされます。",[13,1818,1819],{},"これの良い点は更新日時に応じてほぼ確実にキャッシュを殺すことができると同時に、次の更新まではキャッシュが効くようになることです。ヘッダー・クッキーでは容易にバージョンパラメータを追加するのが難しいので、ファイルであればクエリパラメータがおすすめです。",[13,1821,1822,1823,1812,1825,1827,1828,1830],{},"ちなみにパラメーターパターンに対するキャッシュは効いているので、",[41,1824,1811],{},[41,1826,1815],{},"に変わったとして",[41,1829,1811],{},"でアクセスするとキャッシュ期限まで古いものが表示されます。そのため「更新されたら必ず過去のキャッシュは消えるようにしないといけない」という場合は上記のcloudfrontでのキャッシュ削除が必要です。",{"title":43,"searchDepth":98,"depth":98,"links":1832},[],[820],"2022-12-01","クエリパラメータをもちいてコンテンツキャッシュによって更新が効かない事態を防ぐ",{},"\u002Farticles\u002Fhandle-cloudfront-cache",{"title":1730,"description":1835},"articles\u002Fhandle-cloudfront-cache",[830],"util\u002FArch_Amazon-CloudFront_64@5x.png","-ACQ7N5nl6EKYNvLu0Wqud1YIRKtjV-LoxVZjBGLnKw",{"id":1844,"title":1845,"body":1846,"category":3060,"createdAt":3061,"description":3062,"extension":823,"index":824,"meta":3063,"navigation":94,"path":3064,"publish":94,"seo":3065,"series":824,"seriesTitle":824,"stem":3066,"tag":3067,"thumbnail":3068,"updatedAt":824,"__hash__":3069},"articles\u002Farticles\u002Fstatic-site-search.md","AWS Cloud Searchを使用して静的コンテンツに検索機能をつける",{"type":10,"value":1847,"toc":3045},[1848,1851,1854,1857,1860,1863,1866,1883,1886,1889,1892,1901,1904,1907,1910,1913,1916,1919,1932,1935,2154,2160,2163,2172,2175,2178,2325,2332,2335,2338,2341,2344,2347,2351,2360,2363,2366,2374,2377,2384,2387,2394,2522,2525,2529,2532,2753,2763,2766,2769,2772,2775,2786,2789,2798,2802,2805,2808,2863,2866,2869,2872,2875,2878,2881,2884,3027,3030,3033,3036,3039,3042],[13,1849,1850],{},"こんにちはjunです。静的書き出しブログを作って数ヶ月後、ようやくこのブログにも「検索機能」を実装しました。静的ファイルの場合は検索といった動的な機能をつける際には工夫が必要です。",[13,1852,1853],{},"wordpressなどでは自身のプログラムとデータベースを用いて検索を行いますが、静的ブログではその２つがないので別途で準備する必要があります。つまり検索プログラムと検索対象のファイルをどこかに置き、さらにサイトからAjaxを用いてアクセスできるようにします。",[13,1855,1856],{},"私のサイトでは検索エンジンに「AWS Cloudsearch」というものを用いて実装しています。今回の記事でも同じようにCloudsearchを用いての解説を行います。",[51,1858,1859],{"id":1859},"全体の概要",[898,1861],{":src":1862,":width":901},"'static-site-search\u002Fcloudsearch_build.jpg'",[13,1864,1865],{},"概要は上図のような感じです。検索機能自体はcloudsearchに任せ、cloudsearchを叩くAPI gatewayを通じてブラウザからアクセスします。これらの構成を実現するために以下の手順の実装が必要となります。",[1190,1867,1868,1871,1874,1877,1880],{},[847,1869,1870],{},"cloudsearchのインスタンス作成",[847,1872,1873],{},"検索対象のドキュメント（JSON）をcloudsearchにアップロード",[847,1875,1876],{},"API gateqwayからcloudsearchを叩くように連携",[847,1878,1879],{},"ウェブサイトから検索クエリを持たせたリクエストでAPIを叩く",[847,1881,1882],{},"結果をサイトに表示",[13,1884,1885],{},"それぞれの手順通りに説明していきます。",[51,1887,1888],{"id":1888},"cloudsearchのセットアップ",[598,1890,1891],{"id":1891},"cloudsearchのインスタンスを作成",[13,1893,1894,1895,1900],{},"AWSのアカウント作成などは省略します。AWSのメニューから",[23,1896,1899],{"href":1897,"rel":1898},"https:\u002F\u002Fap-northeast-1.console.aws.amazon.com\u002Fcloudsearch",[27],"cloudsearch","に移動します。リージョンを確認して、「Create a new search domain」をクリックして検索インスタンスを作成します。ここでいうドメインはURLのドメインという意味でなく、「Domain」は検索の区分みたいなものです。",[898,1902],{":src":1903,":width":901},"'static-site-search\u002Fcloudsearch_start.png'",[13,1905,1906],{},"インスタンスの大きさなどを選択できますが、今回は一番小さいものにしておきます。Desired Instance Typeをsearch.small、Desired Replication Countを１にしました。",[598,1908,1909],{"id":1909},"インデックスフィールドの登録",[13,1911,1912],{},"次に検索対象のインデックスフィールドを登録します。",[898,1914],{":src":1915,":width":901},"'static-site-search\u002Fset_document_key_config.png'",[13,1917,1918],{},"ここでいうインデックスフィールドとはタイトル、内容、カテゴリー、作成日時といった各ドキュメントの属性のことをいいます。インデックスフィールドを設定することでタイトルで検索、内容で検索、特定日時からの検索といった複雑な検索ができます。RDBでいうところのカラムみたいなものです。",[13,1920,1921,1922,1925,1926,1931],{},"もしドキュメントがある場合は",[41,1923,1924],{},"Analyze sample file(s) from my local machine","を選択しファイルをアップロードします。使用できるファイルは",[23,1927,1930],{"href":1928,"rel":1929},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002Fcloudsearch\u002Flatest\u002Fdeveloperguide\u002Fpreparing-data.html",[27],"XML,JSON,CSVがサポートされています。（詳細はこちら）","アップロードされたファイルから共通のインデックスフィールドを自動で設定してくれます。",[13,1933,1934],{},"私の場合は以下のようなcloudsearchの使用に従った構成でサンプルJSONを予め作成しておきました。",[34,1936,1938],{"className":961,"code":1937,"language":963,"meta":43,"style":43},"[\n    {\n        \"type\":\"add\",\n        \"id\":\"bag-html-break-tag\",\n        \"fields\":{\n            \"description\":\"white-space： pre;で要素内で生じる、文章の隙間、インテンドの原因。\",\n            \"title\":\"white-space： pre;で要素内で生じる文章の隙間、インテンドの原因。\",\n            \"category\":[\"ministack\"],\n            \"tag\":[\"html\",\"css\",\"vue\"],\n            \"path\":\"https:\u002F\u002Fjun-app.com\u002Farticles\u002Fbag-html-break-tag\",\n            \"content\":\"~~~~~~~~~\"\n        }\n    }\n]\n",[41,1939,1940,1945,1950,1971,1991,2003,2023,2043,2064,2102,2122,2140,2144,2149],{"__ignoreMap":43},[67,1941,1942],{"class":69,"line":70},[67,1943,1944],{"class":77},"[\n",[67,1946,1947],{"class":69,"line":91},[67,1948,1949],{"class":77},"    {\n",[67,1951,1952,1955,1958,1960,1962,1964,1967,1969],{"class":69,"line":98},[67,1953,1954],{"class":77},"        \"",[67,1956,1957],{"class":245},"type",[67,1959,314],{"class":77},[67,1961,78],{"class":77},[67,1963,314],{"class":77},[67,1965,1966],{"class":84},"add",[67,1968,314],{"class":77},[67,1970,317],{"class":77},[67,1972,1973,1975,1978,1980,1982,1984,1987,1989],{"class":69,"line":4},[67,1974,1954],{"class":77},[67,1976,1977],{"class":245},"id",[67,1979,314],{"class":77},[67,1981,78],{"class":77},[67,1983,314],{"class":77},[67,1985,1986],{"class":84},"bag-html-break-tag",[67,1988,314],{"class":77},[67,1990,317],{"class":77},[67,1992,1993,1995,1998,2000],{"class":69,"line":114},[67,1994,1954],{"class":77},[67,1996,1997],{"class":245},"fields",[67,1999,314],{"class":77},[67,2001,2002],{"class":77},":{\n",[67,2004,2005,2007,2010,2012,2014,2016,2019,2021],{"class":69,"line":125},[67,2006,1014],{"class":77},[67,2008,2009],{"class":1017},"description",[67,2011,314],{"class":77},[67,2013,78],{"class":77},[67,2015,314],{"class":77},[67,2017,2018],{"class":84},"white-space： pre;で要素内で生じる、文章の隙間、インテンドの原因。",[67,2020,314],{"class":77},[67,2022,317],{"class":77},[67,2024,2025,2027,2030,2032,2034,2036,2039,2041],{"class":69,"line":136},[67,2026,1014],{"class":77},[67,2028,2029],{"class":1017},"title",[67,2031,314],{"class":77},[67,2033,78],{"class":77},[67,2035,314],{"class":77},[67,2037,2038],{"class":84},"white-space： pre;で要素内で生じる文章の隙間、インテンドの原因。",[67,2040,314],{"class":77},[67,2042,317],{"class":77},[67,2044,2045,2047,2050,2052,2055,2057,2059,2061],{"class":69,"line":144},[67,2046,1014],{"class":77},[67,2048,2049],{"class":1017},"category",[67,2051,314],{"class":77},[67,2053,2054],{"class":77},":[",[67,2056,314],{"class":77},[67,2058,820],{"class":84},[67,2060,314],{"class":77},[67,2062,2063],{"class":77},"],\n",[67,2065,2066,2068,2071,2073,2075,2077,2080,2082,2084,2086,2089,2091,2093,2095,2098,2100],{"class":69,"line":153},[67,2067,1014],{"class":77},[67,2069,2070],{"class":1017},"tag",[67,2072,314],{"class":77},[67,2074,2054],{"class":77},[67,2076,314],{"class":77},[67,2078,2079],{"class":84},"html",[67,2081,314],{"class":77},[67,2083,350],{"class":77},[67,2085,314],{"class":77},[67,2087,2088],{"class":84},"css",[67,2090,314],{"class":77},[67,2092,350],{"class":77},[67,2094,314],{"class":77},[67,2096,2097],{"class":84},"vue",[67,2099,314],{"class":77},[67,2101,2063],{"class":77},[67,2103,2104,2106,2109,2111,2113,2115,2118,2120],{"class":69,"line":164},[67,2105,1014],{"class":77},[67,2107,2108],{"class":1017},"path",[67,2110,314],{"class":77},[67,2112,78],{"class":77},[67,2114,314],{"class":77},[67,2116,2117],{"class":84},"https:\u002F\u002Fjun-app.com\u002Farticles\u002Fbag-html-break-tag",[67,2119,314],{"class":77},[67,2121,317],{"class":77},[67,2123,2124,2126,2129,2131,2133,2135,2138],{"class":69,"line":172},[67,2125,1014],{"class":77},[67,2127,2128],{"class":1017},"content",[67,2130,314],{"class":77},[67,2132,78],{"class":77},[67,2134,314],{"class":77},[67,2136,2137],{"class":84},"~~~~~~~~~",[67,2139,88],{"class":77},[67,2141,2142],{"class":69,"line":180},[67,2143,565],{"class":77},[67,2145,2146],{"class":69,"line":188},[67,2147,2148],{"class":77},"    }\n",[67,2150,2151],{"class":69,"line":196},[67,2152,2153],{"class":77},"]\n",[13,2155,2156,2157,2159],{},"この場合、",[41,2158,1997],{},"にある項目が自動に読みとれ、以下のように設定されます。",[898,2161],{":src":2162,":width":901},"'static-site-search\u002Fconf_key.png'",[13,2164,2165,2166,2171],{},"インデックスフィールドの設定詳細は",[23,2167,2170],{"href":2168,"rel":2169},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002Fcloudsearch\u002Flatest\u002Fdeveloperguide\u002Fconfiguring-index-fields.html",[27],"こちら","を参考にしてください。",[598,2173,2174],{"id":2174},"アクセスポリシーの追加",[13,2176,2177],{},"次にこのcloudsearchインスタンスに対するアクセスポリシーを設定します。",[34,2179,2181],{"className":1628,"code":2180,"language":1631,"meta":43,"style":43},"{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"*\"\n      },\n      \"Action\": [\n        \"cloudsearch:search\",\n        \"cloudsearch:suggest\"\n      ]\n    }\n  ]\n}\n",[41,2182,2183,2187,2206,2218,2222,2241,2253,2270,2275,2287,2298,2307,2312,2316,2321],{"__ignoreMap":43},[67,2184,2185],{"class":69,"line":70},[67,2186,407],{"class":77},[67,2188,2189,2192,2194,2196,2198,2200,2202,2204],{"class":69,"line":91},[67,2190,2191],{"class":77},"  \"",[67,2193,977],{"class":245},[67,2195,314],{"class":77},[67,2197,78],{"class":77},[67,2199,81],{"class":77},[67,2201,986],{"class":84},[67,2203,314],{"class":77},[67,2205,317],{"class":77},[67,2207,2208,2210,2212,2214,2216],{"class":69,"line":98},[67,2209,2191],{"class":77},[67,2211,997],{"class":245},[67,2213,314],{"class":77},[67,2215,78],{"class":77},[67,2217,1004],{"class":77},[67,2219,2220],{"class":69,"line":4},[67,2221,1949],{"class":77},[67,2223,2224,2227,2229,2231,2233,2235,2237,2239],{"class":69,"line":114},[67,2225,2226],{"class":77},"      \"",[67,2228,1038],{"class":1017},[67,2230,314],{"class":77},[67,2232,78],{"class":77},[67,2234,81],{"class":77},[67,2236,1047],{"class":84},[67,2238,314],{"class":77},[67,2240,317],{"class":77},[67,2242,2243,2245,2247,2249,2251],{"class":69,"line":125},[67,2244,2226],{"class":77},[67,2246,1058],{"class":1017},[67,2248,314],{"class":77},[67,2250,78],{"class":77},[67,2252,299],{"class":77},[67,2254,2255,2257,2260,2262,2264,2266,2268],{"class":69,"line":136},[67,2256,1954],{"class":77},[67,2258,2259],{"class":461},"AWS",[67,2261,314],{"class":77},[67,2263,78],{"class":77},[67,2265,81],{"class":77},[67,2267,1067],{"class":84},[67,2269,88],{"class":77},[67,2271,2272],{"class":69,"line":144},[67,2273,2274],{"class":77},"      },\n",[67,2276,2277,2279,2281,2283,2285],{"class":69,"line":153},[67,2278,2226],{"class":77},[67,2280,1078],{"class":1017},[67,2282,314],{"class":77},[67,2284,78],{"class":77},[67,2286,1004],{"class":77},[67,2288,2289,2291,2294,2296],{"class":69,"line":164},[67,2290,1954],{"class":77},[67,2292,2293],{"class":84},"cloudsearch:search",[67,2295,314],{"class":77},[67,2297,317],{"class":77},[67,2299,2300,2302,2305],{"class":69,"line":172},[67,2301,1954],{"class":77},[67,2303,2304],{"class":84},"cloudsearch:suggest",[67,2306,88],{"class":77},[67,2308,2309],{"class":69,"line":180},[67,2310,2311],{"class":77},"      ]\n",[67,2313,2314],{"class":69,"line":188},[67,2315,2148],{"class":77},[67,2317,2318],{"class":69,"line":196},[67,2319,2320],{"class":77},"  ]\n",[67,2322,2323],{"class":69,"line":204},[67,2324,571],{"class":77},[13,2326,2327,2328,2331],{},"もう少し厳密にしたい人は ",[41,2329,2330],{},"\"AWS\": \"*\"","をロールベースにするなどします。これでAWSのサービス、すなわちAPI gatewayがこのcloudsearchの検索機能を使用できるようになりました。",[598,2333,2334],{"id":2334},"セットアップ完了",[13,2336,2337],{},"最後に確認をして適用します。設定の適用には10分ぐらい時間がかかるので気長に待ちます。この時は他の文書をアップロードするなどもできなくなります。",[898,2339],{":src":2340,":width":901},"'static-site-search\u002Ffirst_setuop_complete.png'",[13,2342,2343],{},"このLOADINGという文字がACTIVEに変われば他の操作ができるようになります。",[898,2345],{":src":2346,":width":901},"'static-site-search\u002Floading.png'",[51,2348,2350],{"id":2349},"api-gatewayの準備","API gatewayの準備",[13,2352,2353,2354,2359],{},"cloudsearchの処理が完了する間、API gatewayも実装しましょう。公式では",[23,2355,2358],{"href":2356,"rel":2357},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002Fcloudsearch\u002Flatest\u002Fdeveloperguide\u002Fapi-gateway.html",[27],"「Amazon CloudSearch と API Gateway の統合」"," こちらの記事が大変参考になります。",[13,2361,2362],{},"新しいAPIの作成まで行ったら、GETメソッドのAPIを選択します。そして「統合リクエスト」を選択します。ここでリクエストをAWSのサービスに連絡させます。",[898,2364],{":src":2365,":width":1780,":center":1781},"'static-site-search\u002Fapi_setting.png'",[13,2367,2368,2369,2373],{},"上記の黒塗りにした箇所はcloudsearchのダッシュボードにある値を入れます。「AWSサブドメイン」は「Search Endpoint」、「実行ロール」はAPI gatewayがcloudsearchを叩くためのロールのARNを入れます。そのロールの作成は",[23,2370,2170],{"href":2371,"rel":2372},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002Fcloudsearch\u002Flatest\u002Fdeveloperguide\u002Fapi-gateway.html#api-gateway-pre",[27]," を確認してください。",[898,2375],{":src":2376,":width":1780,":center":1781},"'static-site-search\u002Fdashboard.png'",[13,2378,2379,2380,2383],{},"最後に「URL クエリ文字列パラメータ」に「q」という名前でAPI gatewaryに含まれる ",[41,2381,2382],{},"method.request.querystring.q","をマッピングします。こうすることでクライアントからきたcloudsearchのクエリが、API gatewayを通じて実際のcloudsearchに渡されるようになりました。",[13,2385,2386],{},"次に「統合リクエスト」から一つ前の画面に戻って「メソッドリクエスト」の画面を開き、「リクエストの検証」の項目で「クエリ文字列パラメーターおよびヘッダーの検証」を選択します。そして先ほど設定したクエリパラメータ「q」を必須にするため、「URL クエリ文字列パラメータ」を開いて名前に「q」として必須にして確定します。こうすることで必ずリクエストにはcloudsearchで検索を行うためのクエリ文が含まれるようになりました。",[13,2388,2389,2390,2393],{},"テストを行い、クエリ文字列に",[41,2391,2392],{},"q=test","みたいに入れて送ってみましょう。ステータスが200で以下のようなレスポンスボディがあればcloudsearchが検索結果を返しています。",[34,2395,2397],{"className":1628,"code":2396,"language":1631,"meta":43,"style":43},"{\n  \"status\": {\n    \"rid\": \"~~~~~~\",\n    \"time-ms\": 1\n  },\n  \"hits\": {\n    \"found\": 0,\n    \"start\": 0,\n    \"hit\": []\n  }\n}\n",[41,2398,2399,2403,2416,2436,2450,2455,2468,2484,2499,2513,2518],{"__ignoreMap":43},[67,2400,2401],{"class":69,"line":70},[67,2402,407],{"class":77},[67,2404,2405,2407,2410,2412,2414],{"class":69,"line":91},[67,2406,2191],{"class":77},[67,2408,2409],{"class":245},"status",[67,2411,314],{"class":77},[67,2413,78],{"class":77},[67,2415,299],{"class":77},[67,2417,2418,2420,2423,2425,2427,2429,2432,2434],{"class":69,"line":98},[67,2419,974],{"class":77},[67,2421,2422],{"class":1017},"rid",[67,2424,314],{"class":77},[67,2426,78],{"class":77},[67,2428,81],{"class":77},[67,2430,2431],{"class":84},"~~~~~~",[67,2433,314],{"class":77},[67,2435,317],{"class":77},[67,2437,2438,2440,2443,2445,2447],{"class":69,"line":4},[67,2439,974],{"class":77},[67,2441,2442],{"class":1017},"time-ms",[67,2444,314],{"class":77},[67,2446,78],{"class":77},[67,2448,2449],{"class":461}," 1\n",[67,2451,2452],{"class":69,"line":114},[67,2453,2454],{"class":77},"  },\n",[67,2456,2457,2459,2462,2464,2466],{"class":69,"line":125},[67,2458,2191],{"class":77},[67,2460,2461],{"class":245},"hits",[67,2463,314],{"class":77},[67,2465,78],{"class":77},[67,2467,299],{"class":77},[67,2469,2470,2472,2475,2477,2479,2482],{"class":69,"line":136},[67,2471,974],{"class":77},[67,2473,2474],{"class":1017},"found",[67,2476,314],{"class":77},[67,2478,78],{"class":77},[67,2480,2481],{"class":461}," 0",[67,2483,317],{"class":77},[67,2485,2486,2488,2491,2493,2495,2497],{"class":69,"line":144},[67,2487,974],{"class":77},[67,2489,2490],{"class":1017},"start",[67,2492,314],{"class":77},[67,2494,78],{"class":77},[67,2496,2481],{"class":461},[67,2498,317],{"class":77},[67,2500,2501,2503,2506,2508,2510],{"class":69,"line":153},[67,2502,974],{"class":77},[67,2504,2505],{"class":1017},"hit",[67,2507,314],{"class":77},[67,2509,78],{"class":77},[67,2511,2512],{"class":77}," []\n",[67,2514,2515],{"class":69,"line":164},[67,2516,2517],{"class":77},"  }\n",[67,2519,2520],{"class":69,"line":172},[67,2521,571],{"class":77},[13,2523,2524],{},"何も文書をアップロードしていなければhitしませんし、そしてCloudsearchがLOADINGだと返してくれないことがあります。テストが完了したらAPIをデプロイしてAPI gatewayの設定は完了です。あとはクライアントから適当に叩いてください。",[51,2526,2528],{"id":2527},"文書ページをアップロードする","文書（ページ）をアップロードする",[13,2530,2531],{},"それでは検索させる文書、静的ページの内容をアップロードしましょう。先述の通り以下のような形式のJSONにまとめます。",[34,2533,2535],{"className":961,"code":2534,"language":963,"meta":43,"style":43},"[\n    \u002F\u002F ここから\n    {\n        \"type\":\"add\",\n        \"id\":\"bag-html-break-tag\",\n        \"fields\":{\n            \"description\":\"white-space： pre;で要素内で生じる、文章の隙間、インテンドの原因。\",\n            \"title\":\"white-space： pre;で要素内で生じる文章の隙間、インテンドの原因。\",\n            \"category\":[\"ministack\"],\n            \"tag\":[\"html\",\"css\",\"vue\"],\n            \"path\":\"https:\u002F\u002Fjun-app.com\u002Farticles\u002Fbag-html-break-tag\",\n            \"content\":\"~~~~~~~~~\"\n        }\n    },\n    \u002F\u002F　これで１件の文書を追加するという意味。\n    {\n        ...\n    },\n    ...\n]\n",[41,2536,2537,2541,2546,2550,2568,2586,2596,2614,2632,2650,2684,2702,2718,2722,2727,2732,2736,2741,2745,2749],{"__ignoreMap":43},[67,2538,2539],{"class":69,"line":70},[67,2540,1944],{"class":77},[67,2542,2543],{"class":69,"line":91},[67,2544,2545],{"class":751},"    \u002F\u002F ここから\n",[67,2547,2548],{"class":69,"line":98},[67,2549,1949],{"class":77},[67,2551,2552,2554,2556,2558,2560,2562,2564,2566],{"class":69,"line":4},[67,2553,1954],{"class":77},[67,2555,1957],{"class":245},[67,2557,314],{"class":77},[67,2559,78],{"class":77},[67,2561,314],{"class":77},[67,2563,1966],{"class":84},[67,2565,314],{"class":77},[67,2567,317],{"class":77},[67,2569,2570,2572,2574,2576,2578,2580,2582,2584],{"class":69,"line":114},[67,2571,1954],{"class":77},[67,2573,1977],{"class":245},[67,2575,314],{"class":77},[67,2577,78],{"class":77},[67,2579,314],{"class":77},[67,2581,1986],{"class":84},[67,2583,314],{"class":77},[67,2585,317],{"class":77},[67,2587,2588,2590,2592,2594],{"class":69,"line":125},[67,2589,1954],{"class":77},[67,2591,1997],{"class":245},[67,2593,314],{"class":77},[67,2595,2002],{"class":77},[67,2597,2598,2600,2602,2604,2606,2608,2610,2612],{"class":69,"line":136},[67,2599,1014],{"class":77},[67,2601,2009],{"class":1017},[67,2603,314],{"class":77},[67,2605,78],{"class":77},[67,2607,314],{"class":77},[67,2609,2018],{"class":84},[67,2611,314],{"class":77},[67,2613,317],{"class":77},[67,2615,2616,2618,2620,2622,2624,2626,2628,2630],{"class":69,"line":144},[67,2617,1014],{"class":77},[67,2619,2029],{"class":1017},[67,2621,314],{"class":77},[67,2623,78],{"class":77},[67,2625,314],{"class":77},[67,2627,2038],{"class":84},[67,2629,314],{"class":77},[67,2631,317],{"class":77},[67,2633,2634,2636,2638,2640,2642,2644,2646,2648],{"class":69,"line":153},[67,2635,1014],{"class":77},[67,2637,2049],{"class":1017},[67,2639,314],{"class":77},[67,2641,2054],{"class":77},[67,2643,314],{"class":77},[67,2645,820],{"class":84},[67,2647,314],{"class":77},[67,2649,2063],{"class":77},[67,2651,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674,2676,2678,2680,2682],{"class":69,"line":164},[67,2653,1014],{"class":77},[67,2655,2070],{"class":1017},[67,2657,314],{"class":77},[67,2659,2054],{"class":77},[67,2661,314],{"class":77},[67,2663,2079],{"class":84},[67,2665,314],{"class":77},[67,2667,350],{"class":77},[67,2669,314],{"class":77},[67,2671,2088],{"class":84},[67,2673,314],{"class":77},[67,2675,350],{"class":77},[67,2677,314],{"class":77},[67,2679,2097],{"class":84},[67,2681,314],{"class":77},[67,2683,2063],{"class":77},[67,2685,2686,2688,2690,2692,2694,2696,2698,2700],{"class":69,"line":172},[67,2687,1014],{"class":77},[67,2689,2108],{"class":1017},[67,2691,314],{"class":77},[67,2693,78],{"class":77},[67,2695,314],{"class":77},[67,2697,2117],{"class":84},[67,2699,314],{"class":77},[67,2701,317],{"class":77},[67,2703,2704,2706,2708,2710,2712,2714,2716],{"class":69,"line":180},[67,2705,1014],{"class":77},[67,2707,2128],{"class":1017},[67,2709,314],{"class":77},[67,2711,78],{"class":77},[67,2713,314],{"class":77},[67,2715,2137],{"class":84},[67,2717,88],{"class":77},[67,2719,2720],{"class":69,"line":188},[67,2721,565],{"class":77},[67,2723,2724],{"class":69,"line":196},[67,2725,2726],{"class":77},"    },\n",[67,2728,2729],{"class":69,"line":204},[67,2730,2731],{"class":751},"    \u002F\u002F　これで１件の文書を追加するという意味。\n",[67,2733,2734],{"class":69,"line":212},[67,2735,1949],{"class":77},[67,2737,2738],{"class":69,"line":467},[67,2739,2740],{"class":249},"        ...\n",[67,2742,2743],{"class":69,"line":497},[67,2744,2726],{"class":77},[67,2746,2747],{"class":69,"line":503},[67,2748,1655],{"class":249},[67,2750,2751],{"class":69,"line":521},[67,2752,2153],{"class":77},[13,2754,2755,2756,350,2758,350,2760,2762],{},"ここでアップロードの際には上記のような",[41,2757,1957],{},[41,2759,1977],{},[41,2761,1997],{}," の3つのプロパティーを持つ必要があります。idは重複すると上書きされてしまうので、必ず異なるようにします。また後からわかるように何らかの規則があると、削除や上書きが簡単になります。私の場合は記事のマークダウンファイルの名前から取っています。私のブログはnuxt contentを使用しているのでnode.jsをいじってうんちゃらしています。頑張って文書分のjsonを用意してください。",[598,2764,2765],{"id":2765},"json化する際の注意点",[13,2767,2768],{},"文書のアップロードを行う時に失敗すると以下のような画面が表示されます。",[898,2770],{":src":2771,":width":901,":center":1781},"'static-site-search\u002Ffailed.png'",[13,2773,2774],{},"アップロードが失敗する原因と対策としてこの英文の通り",[1190,2776,2777,2780,2783],{},[847,2778,2779],{},"cloudsearchがACTIVEでなくLOADINGやNEED INDEX状態であるので、ACTIVEになるまで待つこと。（またはindex optionsでインデックスを走らせる）",[847,2781,2782],{},"single valueとして定義されたフィールドになっているのに、文書に複数の値が入っている。（自分もよくわからん）",[847,2784,2785],{},"cloudserachで定義されていないフィールドが文書にある。またはその逆。フィールドの設定をもう一度見直すか、文書のフィールドを設定し直す。",[13,2787,2788],{},"となります。",[13,2790,2791,2792,2797],{},"ただしフィールドなどを正しくしても何度も治らず、いろいろ検索しました。すると",[23,2793,2796],{"href":2794,"rel":2795},"https:\u002F\u002Fdocs.aws.amazon.com\u002Fja_jp\u002Fcloudsearch\u002Flatest\u002Fdeveloperguide\u002Fpreparing-data.html#:~:text=JSON%20%E3%83%90%E3%83%83%E3%83%81%E3%81%A8%20XML%20%E3%83%90%E3%83%83%E3%83%81%E3%81%AB%E3%81%AF%E3%81%A9%E3%81%A1%E3%82%89%E3%82%82%E3%80%81XML%20%E3%81%A7%E6%9C%89%E5%8A%B9%E3%81%AA%20UTF-8%20%E6%96%87%E5%AD%97%E3%81%AE%E3%81%BF%E3%82%92%E5%90%AB%E3%82%81%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82",[27],"公式ドキュメントのこの箇所","に解決策と思われる内容がありました。",[866,2799,2801],{"className":2800},[869,1385],"\nJSON バッチと XML バッチにはどちらも、XML で有効な UTF-8 文字のみを含めることができます。有効な文字は、制御文字のタブ（0009）、復帰（000D）、改行（000A）、および Unicode と ISO\u002FIEC 10646 での有効な文字です。FFFE、FFFF、サロゲートブロックの D800–DBFF と DC00–DFFF は無効で、エラーが発生します （詳細については、『Extensible Markup Language (XML) 1.0 (Fifth Edition)』 を参照してください)。無効な文字に一致する次の正規表現を使用して、無効な文字を削除することができます。\u002F[^\\u0009\\u000a\\u000d\\u0020-\\uD7FF\\uE000-\\uFFFD]\u002F 。\n",[13,2803,2804],{},"つまり作成したJSONに無効なURF-8文字列が含まれており、エラーが起きていたのです。アップロードエラーには表示されないのでかなり詰まりました。",[13,2806,2807],{},"今回のjsonの場合、titleとcontentに対して上記の正規表現を使って無効な文字列を削除してます。node.jsの場合は以下の通りです。",[34,2809,2813],{"className":2810,"code":2811,"language":2812,"meta":43,"style":43},"language-javascript shiki shiki-themes material-theme-ocean","data = data.replace(\n    \u002F[^\\u0009\\u000a\\u000d\\u0020-\\uD7FF\\uE000-\\uFFFD]\u002Fg, \n    ''\n);\n","javascript",[41,2814,2815,2833,2852,2857],{"__ignoreMap":43},[67,2816,2817,2820,2822,2825,2827,2830],{"class":69,"line":70},[67,2818,2819],{"class":249},"data ",[67,2821,253],{"class":77},[67,2823,2824],{"class":249}," data",[67,2826,426],{"class":77},[67,2828,2829],{"class":256},"replace",[67,2831,2832],{"class":249},"(\n",[67,2834,2835,2838,2841,2844,2847,2849],{"class":69,"line":91},[67,2836,2837],{"class":77},"    \u002F[^",[67,2839,2840],{"class":84},"\\u0009\\u000a\\u000d\\u0020-\\uD7FF\\uE000-\\uFFFD",[67,2842,2843],{"class":77},"]\u002F",[67,2845,2846],{"class":461},"g",[67,2848,350],{"class":77},[67,2850,2851],{"class":249}," \n",[67,2853,2854],{"class":69,"line":98},[67,2855,2856],{"class":77},"    ''\n",[67,2858,2859,2861],{"class":69,"line":4},[67,2860,356],{"class":249},[67,2862,282],{"class":77},[13,2864,2865],{},"こうして直したjsonは無事、なんとかアップロードできました。どうやって見つけたかというと、１文書だけをいくつかのパターンに分けてアップロードすると、できるものとできないものがあって「使用される文字列が原因か？」となったのが決定打でした。",[598,2867,2868],{"id":2868},"テスト",[13,2870,2871],{},"Run a Test Searchにてクエリをテストして、文章がきちんと検索されているか、アップロードされているかをチェックしましょう。",[51,2873,2874],{"id":2874},"クライアント側の実装",[13,2876,2877],{},"これで検索機能の準備ができたので、API gatewayから必要なエンドポイントなどを取得してアクセスしてみましょう。",[898,2879],{":src":2880,":width":901,":center":1781},"'static-site-search\u002Fpage_sample.png'",[13,2882,2883],{},"ページ上ではこのようなUIとして、入力したキーワードで検索できるようにします。cloudsearchは複雑なクエリを組むこともできるのですが、今回は単純なものにします。",[34,2885,2887],{"className":2810,"code":2886,"language":2812,"meta":43,"style":43},"await fetch('https:\u002F\u002Fsample.sa-sample-1.amazonaws.com\u002Fv1?q='+this.query,{\n        method:'GET',\n        headers: {\n        'Content-Type': 'application\u002Fjson'\n        },\n    })\n.then(response => response.json())\n.then(jsondata => {\n    this.result = jsondata\n)\n",[41,2888,2889,2915,2931,2940,2961,2966,2973,2996,3011,3023],{"__ignoreMap":43},[67,2890,2891,2894,2897,2899,2901,2904,2906,2909,2912],{"class":69,"line":70},[67,2892,2893],{"class":403},"await",[67,2895,2896],{"class":256}," fetch",[67,2898,260],{"class":249},[67,2900,263],{"class":77},[67,2902,2903],{"class":84},"https:\u002F\u002Fsample.sa-sample-1.amazonaws.com\u002Fv1?q=",[67,2905,263],{"class":77},[67,2907,2908],{"class":77},"+this.",[67,2910,2911],{"class":249},"query",[67,2913,2914],{"class":77},",{\n",[67,2916,2917,2920,2922,2924,2927,2929],{"class":69,"line":91},[67,2918,2919],{"class":73},"        method",[67,2921,78],{"class":77},[67,2923,263],{"class":77},[67,2925,2926],{"class":84},"GET",[67,2928,263],{"class":77},[67,2930,317],{"class":77},[67,2932,2933,2936,2938],{"class":69,"line":98},[67,2934,2935],{"class":73},"        headers",[67,2937,78],{"class":77},[67,2939,299],{"class":77},[67,2941,2942,2945,2948,2950,2952,2955,2958],{"class":69,"line":4},[67,2943,2944],{"class":77},"        '",[67,2946,2947],{"class":73},"Content-Type",[67,2949,263],{"class":77},[67,2951,78],{"class":77},[67,2953,2954],{"class":77}," '",[67,2956,2957],{"class":84},"application\u002Fjson",[67,2959,2960],{"class":77},"'\n",[67,2962,2963],{"class":69,"line":114},[67,2964,2965],{"class":77},"        },\n",[67,2967,2968,2971],{"class":69,"line":125},[67,2969,2970],{"class":77},"    }",[67,2972,271],{"class":249},[67,2974,2975,2977,2980,2982,2985,2987,2989,2991,2993],{"class":69,"line":136},[67,2976,426],{"class":77},[67,2978,2979],{"class":256},"then",[67,2981,260],{"class":249},[67,2983,2984],{"class":346},"response",[67,2986,359],{"class":245},[67,2988,279],{"class":249},[67,2990,426],{"class":77},[67,2992,1631],{"class":256},[67,2994,2995],{"class":249},"())\n",[67,2997,2998,3000,3002,3004,3007,3009],{"class":69,"line":144},[67,2999,426],{"class":77},[67,3001,2979],{"class":256},[67,3003,260],{"class":249},[67,3005,3006],{"class":346},"jsondata",[67,3008,359],{"class":245},[67,3010,299],{"class":77},[67,3012,3013,3016,3018,3020],{"class":69,"line":153},[67,3014,3015],{"class":77},"    this.",[67,3017,485],{"class":249},[67,3019,337],{"class":77},[67,3021,3022],{"class":249}," jsondata\n",[67,3024,3025],{"class":69,"line":164},[67,3026,271],{"class":73},[13,3028,3029],{},"方法は単純でバリデーションを行った後に、API gatewayのエンドポイントにクエリ付きでアクセスします。",[898,3031],{":src":3032,":width":901,":center":1781},"'static-site-search\u002Fresult.png'",[13,3034,3035],{},"ページのURLなどもフィールドを含まれており、レスポンスで返すようにしています。それを用いてこのように一覧のリンクを作れば検索機能は実装完了です。",[13,3037,3038],{},"今回は検索エンジンにAWS cloudsearchを使用しましたが、他にも文書検索サービスはたくさんあります。商用で利用する場合は検索文書を自動的にアップロードしたりする機能や、より複雑なクエリの構築、キャッシュの利用が必要となります。",[13,3040,3041],{},"以上が静的ページに検索機能を持たせる方法です。最後見てくださりありがとうございました。",[809,3043,3044],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}",{"title":43,"searchDepth":98,"depth":98,"links":3046},[3047,3048,3054,3055,3059],{"id":1859,"depth":91,"text":1859},{"id":1888,"depth":91,"text":1888,"children":3049},[3050,3051,3052,3053],{"id":1891,"depth":98,"text":1891},{"id":1909,"depth":98,"text":1909},{"id":2174,"depth":98,"text":2174},{"id":2334,"depth":98,"text":2334},{"id":2349,"depth":91,"text":2350},{"id":2527,"depth":91,"text":2528,"children":3056},[3057,3058],{"id":2765,"depth":98,"text":2765},{"id":2868,"depth":98,"text":2868},{"id":2874,"depth":91,"text":2874},[1717],"2021-06-28","Google Search以外にも方法があります！AWS Cloud Searchを使って静的サイトに検索機能をつける。",{},"\u002Farticles\u002Fstatic-site-search",{"title":1845,"description":3062},"articles\u002Fstatic-site-search",[830,238],"static-site-search\u002Fthumbnail.png","CvPv7DEIqq4ZRH1_F9I9qcalQq9c5xGj_OltNaXdZ3k",1780987136866]