[{"data":1,"prerenderedAt":2356},["ShallowReactive",2],{"blog-posts":3},[4,691],{"id":5,"title":6,"body":7,"date":678,"description":17,"extension":679,"meta":680,"navigation":209,"path":681,"seo":682,"stem":683,"tags":684,"__hash__":690},"blog/blog/vue-3-migration.md","Vue 3 Migration Pain Points",{"type":8,"value":9,"toc":673},"minimal",[10,14,18,37,42,51,54,87,96,108,521,525,534,540,544,547,632,651,654,669],[11,12,6],"h1",{"id":13},"vue-3-migration-pain-points",[15,16,17],"p",{},"99% of the time, migrating to a new framework will be painful. Upgrading from Vue 2 to Vue 3 is no exception. My company recently upgraded their Vue monorepo of about 30 modules to Vue 3. I wanted to share some of the lessons I've learned along the way.",[15,19,20,21,28,29,36],{},"Before I do, I wanted to give kudos to the Vue team for creating ",[22,23,27],"a",{"href":24,"rel":25},"https://v3-migration.vuejs.org/",[26],"nofollow","Vue Compat and creating an extensive migration guide",". Without this, it would've been ",[30,31,32],"strong",{},[33,34,35],"em",{},"a lot"," more difficult to upgrade. I highly recommend reading this guide carefully.",[38,39,41],"h2",{"id":40},"a-new-reactivity-model","A New Reactivity Model",[15,43,44,45,50],{},"One of the biggest changes is that Vue 2 changes uses a reactivity model involving Object.defineProperty. Now, Vue 3 uses ",[22,46,49],{"href":47,"rel":48},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy",[26],"Proxies"," as wrappers and utilizes get/set overrides to easily track reactivity updates.",[15,52,53],{},"One of the big advantages to Proxies is when you need to track new properties. In Vue 2, you would typically need to write some code to get your reactive object to track a new property. That would look something like this.",[55,56,61],"pre",{"className":57,"code":58,"language":59,"meta":60,"style":60},"language-js shiki shiki-themes github-dark","    Vue.set(yourReactiveObject, 'newProperty', value)\n","js","",[62,63,64],"code",{"__ignoreMap":60},[65,66,69,73,77,80,84],"span",{"class":67,"line":68},"line",1,[65,70,72],{"class":71},"s95oV","    Vue.",[65,74,76],{"class":75},"svObZ","set",[65,78,79],{"class":71},"(yourReactiveObject, ",[65,81,83],{"class":82},"sU2Wk","'newProperty'",[65,85,86],{"class":71},", value)\n",[15,88,89,90,95],{},"But in Vue 3, new properties are automatically track in the set override. And that's great! But unfortunately, the change from reactive objects to Proxies also broke a lot of our code. We relied on the fact that reactive objects were serializable and typically sent them via ",[22,91,94],{"href":92,"rel":93},"https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage",[26],"postMessages"," to iframes that were listening for these messages. But, alas, Proxies are not serializable.",[15,97,98,99,104,105,107],{},"Typically, you can use ",[22,100,103],{"href":101,"rel":102},"https://vuejs.org/api/reactivity-advanced#toraw",[26],"toRaw"," to grab the original object. We created a utility method since ",[62,106,103],{}," doesn't unwrap nested objects.",[55,109,111],{"className":57,"code":110,"language":59,"meta":60,"style":60},"const objectIterator = (input: unknown): unknown => {\n  if (isRef(input) || isReactive(input) || isProxy(input)) {\n    return objectIterator(toRaw(input))\n  }\n\n  if (Array.isArray(input)) {\n    return input.map((item) => objectIterator(item))\n  }\n\n  if (input && typeof input === 'object') {\n    return Object.fromEntries(\n      Object.entries(input)\n        .map(([key, value]) => [key, objectIterator(value)]),\n    )\n  }\n\n  return input\n}\n\nexport const toRawDeep = (sourceObj: Record\u003Cstring, unknown>): Record\u003Cstring, unknown> => {\n  const returnObject = objectIterator(sourceObj)\n\n  if (!isRecord(returnObject)) {\n    throw new TypeError('There was a problem unwrapping your reactive object/ref')\n  }\n\n  return returnObject\n}\n",[62,112,113,152,182,198,204,211,224,252,257,262,288,302,314,348,354,359,364,373,379,384,441,457,462,478,498,503,508,516],{"__ignoreMap":60},[65,114,115,119,122,125,128,132,135,139,142,144,146,149],{"class":67,"line":68},[65,116,118],{"class":117},"snl16","const",[65,120,121],{"class":75}," objectIterator",[65,123,124],{"class":117}," =",[65,126,127],{"class":71}," (",[65,129,131],{"class":130},"s9osk","input",[65,133,134],{"class":117},":",[65,136,138],{"class":137},"sDLfK"," unknown",[65,140,141],{"class":71},")",[65,143,134],{"class":117},[65,145,138],{"class":137},[65,147,148],{"class":117}," =>",[65,150,151],{"class":71}," {\n",[65,153,155,158,160,163,166,169,172,174,176,179],{"class":67,"line":154},2,[65,156,157],{"class":117},"  if",[65,159,127],{"class":71},[65,161,162],{"class":75},"isRef",[65,164,165],{"class":71},"(input) ",[65,167,168],{"class":117},"||",[65,170,171],{"class":75}," isReactive",[65,173,165],{"class":71},[65,175,168],{"class":117},[65,177,178],{"class":75}," isProxy",[65,180,181],{"class":71},"(input)) {\n",[65,183,185,188,190,193,195],{"class":67,"line":184},3,[65,186,187],{"class":117},"    return",[65,189,121],{"class":75},[65,191,192],{"class":71},"(",[65,194,103],{"class":75},[65,196,197],{"class":71},"(input))\n",[65,199,201],{"class":67,"line":200},4,[65,202,203],{"class":71},"  }\n",[65,205,207],{"class":67,"line":206},5,[65,208,210],{"emptyLinePlaceholder":209},true,"\n",[65,212,214,216,219,222],{"class":67,"line":213},6,[65,215,157],{"class":117},[65,217,218],{"class":71}," (Array.",[65,220,221],{"class":75},"isArray",[65,223,181],{"class":71},[65,225,227,229,232,235,238,241,244,247,249],{"class":67,"line":226},7,[65,228,187],{"class":117},[65,230,231],{"class":71}," input.",[65,233,234],{"class":75},"map",[65,236,237],{"class":71},"((",[65,239,240],{"class":130},"item",[65,242,243],{"class":71},") ",[65,245,246],{"class":117},"=>",[65,248,121],{"class":75},[65,250,251],{"class":71},"(item))\n",[65,253,255],{"class":67,"line":254},8,[65,256,203],{"class":71},[65,258,260],{"class":67,"line":259},9,[65,261,210],{"emptyLinePlaceholder":209},[65,263,265,267,270,273,276,279,282,285],{"class":67,"line":264},10,[65,266,157],{"class":117},[65,268,269],{"class":71}," (input ",[65,271,272],{"class":117},"&&",[65,274,275],{"class":117}," typeof",[65,277,278],{"class":71}," input ",[65,280,281],{"class":117},"===",[65,283,284],{"class":82}," 'object'",[65,286,287],{"class":71},") {\n",[65,289,291,293,296,299],{"class":67,"line":290},11,[65,292,187],{"class":117},[65,294,295],{"class":71}," Object.",[65,297,298],{"class":75},"fromEntries",[65,300,301],{"class":71},"(\n",[65,303,305,308,311],{"class":67,"line":304},12,[65,306,307],{"class":71},"      Object.",[65,309,310],{"class":75},"entries",[65,312,313],{"class":71},"(input)\n",[65,315,317,320,322,325,328,331,334,337,339,342,345],{"class":67,"line":316},13,[65,318,319],{"class":71},"        .",[65,321,234],{"class":75},[65,323,324],{"class":71},"(([",[65,326,327],{"class":130},"key",[65,329,330],{"class":71},", ",[65,332,333],{"class":130},"value",[65,335,336],{"class":71},"]) ",[65,338,246],{"class":117},[65,340,341],{"class":71}," [key, ",[65,343,344],{"class":75},"objectIterator",[65,346,347],{"class":71},"(value)]),\n",[65,349,351],{"class":67,"line":350},14,[65,352,353],{"class":71},"    )\n",[65,355,357],{"class":67,"line":356},15,[65,358,203],{"class":71},[65,360,362],{"class":67,"line":361},16,[65,363,210],{"emptyLinePlaceholder":209},[65,365,367,370],{"class":67,"line":366},17,[65,368,369],{"class":117},"  return",[65,371,372],{"class":71}," input\n",[65,374,376],{"class":67,"line":375},18,[65,377,378],{"class":71},"}\n",[65,380,382],{"class":67,"line":381},19,[65,383,210],{"emptyLinePlaceholder":209},[65,385,387,390,393,396,398,400,403,405,408,411,414,416,419,422,424,426,428,430,432,434,437,439],{"class":67,"line":386},20,[65,388,389],{"class":117},"export",[65,391,392],{"class":117}," const",[65,394,395],{"class":75}," toRawDeep",[65,397,124],{"class":117},[65,399,127],{"class":71},[65,401,402],{"class":130},"sourceObj",[65,404,134],{"class":117},[65,406,407],{"class":75}," Record",[65,409,410],{"class":71},"\u003C",[65,412,413],{"class":137},"string",[65,415,330],{"class":71},[65,417,418],{"class":137},"unknown",[65,420,421],{"class":71},">)",[65,423,134],{"class":117},[65,425,407],{"class":75},[65,427,410],{"class":71},[65,429,413],{"class":137},[65,431,330],{"class":71},[65,433,418],{"class":137},[65,435,436],{"class":71},"> ",[65,438,246],{"class":117},[65,440,151],{"class":71},[65,442,444,447,450,452,454],{"class":67,"line":443},21,[65,445,446],{"class":117},"  const",[65,448,449],{"class":137}," returnObject",[65,451,124],{"class":117},[65,453,121],{"class":75},[65,455,456],{"class":71},"(sourceObj)\n",[65,458,460],{"class":67,"line":459},22,[65,461,210],{"emptyLinePlaceholder":209},[65,463,465,467,469,472,475],{"class":67,"line":464},23,[65,466,157],{"class":117},[65,468,127],{"class":71},[65,470,471],{"class":117},"!",[65,473,474],{"class":75},"isRecord",[65,476,477],{"class":71},"(returnObject)) {\n",[65,479,481,484,487,490,492,495],{"class":67,"line":480},24,[65,482,483],{"class":117},"    throw",[65,485,486],{"class":117}," new",[65,488,489],{"class":75}," TypeError",[65,491,192],{"class":71},[65,493,494],{"class":82},"'There was a problem unwrapping your reactive object/ref'",[65,496,497],{"class":71},")\n",[65,499,501],{"class":67,"line":500},25,[65,502,203],{"class":71},[65,504,506],{"class":67,"line":505},26,[65,507,210],{"emptyLinePlaceholder":209},[65,509,511,513],{"class":67,"line":510},27,[65,512,369],{"class":117},[65,514,515],{"class":71}," returnObject\n",[65,517,519],{"class":67,"line":518},28,[65,520,378],{"class":71},[38,522,524],{"id":523},"no-more-arbitrary-route-params","No More Arbitrary Route Params",[15,526,527,528,533],{},"This is actually not a Vue 3 pain point, but rather a Vue Router (version 4) pain point. In our app, we would sometimes use router params as a way to temporarily store data in-between routes. However, this was determined by the Vue Router team to be an anti-pattern since ",[22,529,532],{"href":530,"rel":531},"https://github.com/vuejs/router/blob/main/packages/router/CHANGELOG.md#414-2022-08-22",[26],"reloading the page causes you to lose params",".",[15,535,536,537,539],{},"Since the data is temporary, we wouldn't too keen on adding it to our global stores. Instead, we decided to utilize the History API. Similar to Proxies and ",[62,538,103],{},", this also has issues with serialization since Javascript objects !== JSON. But, for what we're using it for, this has worked well for us.",[38,541,543],{"id":542},"vue-compat-not-adding-event-listeners-to-child-components","Vue Compat Not Adding Event Listeners to Child Components",[15,545,546],{},"Just to reiterate what I said before, Vue Compat has been a fantastic tool in our Vue 3 migration. However, it is a complex package that essentially tries to make Vue 2 code compatible with Vue 3 code, so we expected some errors to occur. One of the most enigmatic issues we saw is when we noticed that events weren't being added to child components. For example, here is an onClick event listener that wasn't be added to the HelloWorld component.",[55,548,550],{"className":57,"code":549,"language":59,"meta":60,"style":60},"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cimg alt=\"Vue logo\" src=\"./assets/logo.png\" />\n    \u003CHelloWorld @click=\"onClick\" />\n  \u003C/div>\n\u003C/template>\n",[62,551,552,563,573,601,614,623],{"__ignoreMap":60},[65,553,554,556,560],{"class":67,"line":68},[65,555,410],{"class":71},[65,557,559],{"class":558},"s4JwU","template",[65,561,562],{"class":71},">\n",[65,564,565,568,571],{"class":67,"line":154},[65,566,567],{"class":71},"  \u003C",[65,569,570],{"class":558},"div",[65,572,562],{"class":71},[65,574,575,578,581,584,587,590,593,595,598],{"class":67,"line":184},[65,576,577],{"class":71},"    \u003C",[65,579,580],{"class":558},"img",[65,582,583],{"class":75}," alt",[65,585,586],{"class":117},"=",[65,588,589],{"class":82},"\"Vue logo\"",[65,591,592],{"class":75}," src",[65,594,586],{"class":117},[65,596,597],{"class":82},"\"./assets/logo.png\"",[65,599,600],{"class":71}," />\n",[65,602,603,605,608,612],{"class":67,"line":200},[65,604,577],{"class":71},[65,606,607],{"class":137},"HelloWorld",[65,609,611],{"class":610},"s6RL2"," @click=\"onClick\"",[65,613,600],{"class":71},[65,615,616,619,621],{"class":67,"line":206},[65,617,618],{"class":71},"  \u003C/",[65,620,570],{"class":558},[65,622,562],{"class":71},[65,624,625,628,630],{"class":67,"line":213},[65,626,627],{"class":71},"\u003C/",[65,629,559],{"class":558},[65,631,562],{"class":71},[15,633,634,635,638,639,642,643,645,646],{},"The key change here is that in Vue 2, you could access attributes via ",[62,636,637],{},"this.$attrs"," and event listeners via ",[62,640,641],{},"this.$listeners",". But in Vue 3, these are both accessible via ",[62,644,637],{},". ",[22,647,650],{"href":648,"rel":649},"https://github.com/vuejs/core/issues/4566#issuecomment-917997056",[26],"LinusBorg gives a great summary on what the issue is and how to fix it.",[15,652,653],{},"Hopefully this post has been a little bit helpful! 😊 The Vue 3 migration is painful but it's well worth it to continue receiving all the new updates and securities. As a reminder:",[655,656,657,661],"ul",{},[658,659,660],"li",{},"Vue 2 went on EOL on December 31st, 2023.",[658,662,663,664,533],{},"There is a ",[22,665,668],{"href":666,"rel":667},"https://www.cve.org/CVERecord?id=CVE-2024-6783",[26],"known CVE in Vue 2",[670,671,672],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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 .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s6RL2, html code.shiki .s6RL2{--shiki-default:#FDAEB7;--shiki-default-font-style:italic}",{"title":60,"searchDepth":154,"depth":154,"links":674},[675,676,677],{"id":40,"depth":154,"text":41},{"id":523,"depth":154,"text":524},{"id":542,"depth":154,"text":543},"2025-09-21T00:00:00.000Z","md",{},"/blog/vue-3-migration",{"title":6,"description":17},"blog/vue-3-migration",[685,686,687,688,689],"vue","vue router","vue compat","vue 3","migration","XbKUGUf23ITb6b6kyluj9waJIU1riTSX7yCVUv2NLBk",{"id":692,"title":693,"body":694,"date":2344,"description":2345,"extension":679,"meta":2346,"navigation":209,"path":2347,"seo":2348,"stem":2349,"tags":2350,"__hash__":2355},"blog/blog/pulumi-static-site-deployment.md","Deploying a Static Site Using Nuxt, Pulumi (Infrastructure as Code), and AWS 🚀",{"type":8,"value":695,"toc":2339},[696,699,714,718,721,742,746,755,907,915,919,928,945,948,1051,1060,1563,1566,1995,1998,2321,2324,2333,2336],[11,697,693],{"id":698},"deploying-a-static-site-using-nuxt-pulumi-infrastructure-as-code-and-aws",[15,700,701,702,707,708,713],{},"While updating my portfolio, I stumbled across ",[22,703,706],{"href":704,"rel":705},"https://dev.to/devteam/announcing-the-pulumi-deploy-and-document-challenge-3000-in-prizes-887",[26],"a post on dev.to about Pulumi",". I found Pulumi interesting because how it makes ",[22,709,712],{"href":710,"rel":711},"https://en.wikipedia.org/wiki/Infrastructure_as_code",[26],"infrastructure as code"," more approachable with libraries in popular programming languages (TypeScript, Java, Go, etc.). Hopefully, I'd also be able to execute this script in any of my future apps for easy server provisioning.",[38,715,717],{"id":716},"goals-for-automated-static-site-deployment","Goals for Automated Static Site Deployment",[15,719,720],{},"In order for me to consider this successful, each deployment should have the following:",[655,722,723,731,739],{},[658,724,725,726],{},"A private S3 bucket to hold static files.\n",[655,727,728],{},[658,729,730],{},"In case we decide to place confidential files in this bucket, we should avoid setting the bucket to public read.",[658,732,733,734],{},"A Cloudfront CDN that mirrors the S3 bucket.\n",[655,735,736],{},[658,737,738],{},"Having a CDN for static sites is always a good thing, since it significantly improves load times due to caching and edge networks.",[658,740,741],{},"Valid certificates and Route 53 Alias entries.",[38,743,745],{"id":744},"configuring-pulumi-with-aws","Configuring Pulumi with AWS",[15,747,748,749,754],{},"Before starting, we want to make sure we have an IAM user set up with the correct permissions. Pulumi uses this user to successfuly create the resources we need. AWS ",[22,750,753],{"href":751,"rel":752},"https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html",[26],"has a helpful guide to do so",". These are the permissions that I set up for my IAM user.",[55,756,760],{"className":757,"code":758,"language":759,"meta":60,"style":60},"language-json shiki shiki-themes github-dark","{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"s3:CreateBucket\",\n                \"s3:PutBucketOwnershipControls\",\n                \"s3:PutBucketPublicAccessBlock\",\n                \"s3:PutBucketAcl\",\n                \"s3:PutObject\",\n                \"s3:PutObjectAcl\",\n                \"s3:GetObject\",\n                \"s3:ListBucket\",\n                \"s3:GetBucketLocation\"\n            ],\n            \"Resource\": [\"*\"]\n        }\n    ]\n}\n","json",[62,761,762,767,781,789,794,806,813,820,827,834,841,848,855,862,869,874,879,893,898,903],{"__ignoreMap":60},[65,763,764],{"class":67,"line":68},[65,765,766],{"class":71},"{\n",[65,768,769,772,775,778],{"class":67,"line":154},[65,770,771],{"class":137},"    \"Version\"",[65,773,774],{"class":71},": ",[65,776,777],{"class":82},"\"2012-10-17\"",[65,779,780],{"class":71},",\n",[65,782,783,786],{"class":67,"line":184},[65,784,785],{"class":137},"    \"Statement\"",[65,787,788],{"class":71},": [\n",[65,790,791],{"class":67,"line":200},[65,792,793],{"class":71},"        {\n",[65,795,796,799,801,804],{"class":67,"line":206},[65,797,798],{"class":137},"            \"Effect\"",[65,800,774],{"class":71},[65,802,803],{"class":82},"\"Allow\"",[65,805,780],{"class":71},[65,807,808,811],{"class":67,"line":213},[65,809,810],{"class":137},"            \"Action\"",[65,812,788],{"class":71},[65,814,815,818],{"class":67,"line":226},[65,816,817],{"class":82},"                \"s3:CreateBucket\"",[65,819,780],{"class":71},[65,821,822,825],{"class":67,"line":254},[65,823,824],{"class":82},"                \"s3:PutBucketOwnershipControls\"",[65,826,780],{"class":71},[65,828,829,832],{"class":67,"line":259},[65,830,831],{"class":82},"                \"s3:PutBucketPublicAccessBlock\"",[65,833,780],{"class":71},[65,835,836,839],{"class":67,"line":264},[65,837,838],{"class":82},"                \"s3:PutBucketAcl\"",[65,840,780],{"class":71},[65,842,843,846],{"class":67,"line":290},[65,844,845],{"class":82},"                \"s3:PutObject\"",[65,847,780],{"class":71},[65,849,850,853],{"class":67,"line":304},[65,851,852],{"class":82},"                \"s3:PutObjectAcl\"",[65,854,780],{"class":71},[65,856,857,860],{"class":67,"line":316},[65,858,859],{"class":82},"                \"s3:GetObject\"",[65,861,780],{"class":71},[65,863,864,867],{"class":67,"line":350},[65,865,866],{"class":82},"                \"s3:ListBucket\"",[65,868,780],{"class":71},[65,870,871],{"class":67,"line":356},[65,872,873],{"class":82},"                \"s3:GetBucketLocation\"\n",[65,875,876],{"class":67,"line":361},[65,877,878],{"class":71},"            ],\n",[65,880,881,884,887,890],{"class":67,"line":366},[65,882,883],{"class":137},"            \"Resource\"",[65,885,886],{"class":71},": [",[65,888,889],{"class":82},"\"*\"",[65,891,892],{"class":71},"]\n",[65,894,895],{"class":67,"line":375},[65,896,897],{"class":71},"        }\n",[65,899,900],{"class":67,"line":381},[65,901,902],{"class":71},"    ]\n",[65,904,905],{"class":67,"line":386},[65,906,378],{"class":71},[15,908,909,910],{},"Next, you'll need to setup configuration so that Pulumi can interact with AWS. ",[22,911,914],{"href":912,"rel":913},"https://www.pulumi.com/registry/packages/aws/installation-configuration/",[26],"Instructions can be found here.",[38,916,918],{"id":917},"installation","Installation",[15,920,921,922,927],{},"The first step is to create a new Pulumi project using the ",[22,923,926],{"href":924,"rel":925},"https://github.com/pulumi/templates/tree/master/aws-typescript",[26],"AWS TypeScript template",". This will scaffold a barebones project that we can use TypeScript to customize after we answer a few basic questions from the prompt.",[55,929,933],{"className":930,"code":931,"language":932,"meta":60,"style":60},"language-shell shiki shiki-themes github-dark","mkdir deployment & cd deployment\npulumi new aws-typescript\n","shell",[62,934,935,940],{"__ignoreMap":60},[65,936,937],{"class":67,"line":68},[65,938,939],{},"mkdir deployment & cd deployment\n",[65,941,942],{"class":67,"line":154},[65,943,944],{},"pulumi new aws-typescript\n",[15,946,947],{},"In this newly scaffolded project, there will be an index.ts file that acts as the entry point for your deployment.",[55,949,951],{"className":57,"code":950,"language":59,"meta":60,"style":60},"// This creates the S3 bucket which will host our static files.\nconst bucket = new aws.s3.BucketV2('my-bucket')\n\nconst websiteConfiguration = new aws.s3.BucketWebsiteConfigurationV2(\n    'website-configuration',\n    {\n        bucket: bucket.id,\n        indexDocument: {\n            suffix: 'index.html',\n        },\n    }\n)\n",[62,952,953,959,983,987,1005,1012,1017,1022,1027,1037,1042,1047],{"__ignoreMap":60},[65,954,955],{"class":67,"line":68},[65,956,958],{"class":957},"sAwPA","// This creates the S3 bucket which will host our static files.\n",[65,960,961,963,966,968,970,973,976,978,981],{"class":67,"line":154},[65,962,118],{"class":117},[65,964,965],{"class":137}," bucket",[65,967,124],{"class":117},[65,969,486],{"class":117},[65,971,972],{"class":71}," aws.s3.",[65,974,975],{"class":75},"BucketV2",[65,977,192],{"class":71},[65,979,980],{"class":82},"'my-bucket'",[65,982,497],{"class":71},[65,984,985],{"class":67,"line":184},[65,986,210],{"emptyLinePlaceholder":209},[65,988,989,991,994,996,998,1000,1003],{"class":67,"line":200},[65,990,118],{"class":117},[65,992,993],{"class":137}," websiteConfiguration",[65,995,124],{"class":117},[65,997,486],{"class":117},[65,999,972],{"class":71},[65,1001,1002],{"class":75},"BucketWebsiteConfigurationV2",[65,1004,301],{"class":71},[65,1006,1007,1010],{"class":67,"line":206},[65,1008,1009],{"class":82},"    'website-configuration'",[65,1011,780],{"class":71},[65,1013,1014],{"class":67,"line":213},[65,1015,1016],{"class":71},"    {\n",[65,1018,1019],{"class":67,"line":226},[65,1020,1021],{"class":71},"        bucket: bucket.id,\n",[65,1023,1024],{"class":67,"line":254},[65,1025,1026],{"class":71},"        indexDocument: {\n",[65,1028,1029,1032,1035],{"class":67,"line":259},[65,1030,1031],{"class":71},"            suffix: ",[65,1033,1034],{"class":82},"'index.html'",[65,1036,780],{"class":71},[65,1038,1039],{"class":67,"line":264},[65,1040,1041],{"class":71},"        },\n",[65,1043,1044],{"class":67,"line":290},[65,1045,1046],{"class":71},"    }\n",[65,1048,1049],{"class":67,"line":304},[65,1050,497],{"class":71},[15,1052,1053,1054,1059],{},"The next portion of the index file has to do with setting up valid certificate endpoints for your DNS (assuming you've created DNS records through AWS Route 53). Note that while your other services (such as your S3 bucket) can be in your desired region, your certificates ",[30,1055,1056],{},[33,1057,1058],{},"must"," be created in the us-east-1 region.",[55,1061,1063],{"className":57,"code":1062,"language":59,"meta":60,"style":60},"const hostedZoneId = aws.route53\n    .getZone({ name: 'yourdomain.com' }, { async: true })\n    .then((zone) => zone.zoneId)\n\n// Per AWS, ACM certificate must be in the us-east-1 region.\nconst eastRegion = new aws.Provider('east', {\n    profile: aws.config.profile,\n    region: 'us-east-1',\n})\n\nconst certificateConfig: aws.acm.CertificateArgs = {\n    domainName: 'yourdomain.com',\n    validationMethod: 'DNS',\n    subjectAlternativeNames: ['www.yourdomain.com'],\n}\n\nconst certificate = new aws.acm.Certificate('certificate', certificateConfig, {\n    provider: eastRegion,\n})\n\nconst certificateValidationDomain = new aws.route53.Record(\n    `yourdomain.com-validation`,\n    {\n        name: certificate.domainValidationOptions[0].resourceRecordName,\n        zoneId: hostedZoneId,\n        type: certificate.domainValidationOptions[0].resourceRecordType,\n        records: [certificate.domainValidationOptions[0].resourceRecordValue],\n        ttl: 60 * 10,\n    }\n)\n\nconst wwwCertificateValidationDomain = new aws.route53.Record(\n    `www.yourdomain.com-validation`,\n    {\n        name: certificate.domainValidationOptions[1].resourceRecordName,\n        zoneId: hostedZoneId,\n        type: certificate.domainValidationOptions[1].resourceRecordType,\n        records: [certificate.domainValidationOptions[1].resourceRecordValue],\n        ttl: 60 * 10,\n    }\n)\n\nconst certificateValidation = new aws.acm.CertificateValidation(\n    'certificateValidation',\n    {\n        certificateArn: certificate.arn,\n        validationRecordFqdns: [\n            certificateValidationDomain.fqdn,\n            wwwCertificateValidationDomain.fqdn,\n        ],\n    },\n    { provider: eastRegion }\n)\n\ncertificateArn = certificateValidation.certificateArn\n",[62,1064,1065,1077,1100,1119,1123,1128,1153,1158,1168,1173,1177,1203,1212,1222,1233,1237,1241,1266,1271,1275,1279,1298,1305,1309,1320,1325,1335,1345,1361,1366,1371,1376,1394,1402,1407,1417,1422,1431,1440,1453,1458,1463,1468,1487,1495,1500,1506,1512,1518,1524,1530,1536,1542,1547,1552],{"__ignoreMap":60},[65,1066,1067,1069,1072,1074],{"class":67,"line":68},[65,1068,118],{"class":117},[65,1070,1071],{"class":137}," hostedZoneId",[65,1073,124],{"class":117},[65,1075,1076],{"class":71}," aws.route53\n",[65,1078,1079,1082,1085,1088,1091,1094,1097],{"class":67,"line":154},[65,1080,1081],{"class":71},"    .",[65,1083,1084],{"class":75},"getZone",[65,1086,1087],{"class":71},"({ name: ",[65,1089,1090],{"class":82},"'yourdomain.com'",[65,1092,1093],{"class":71}," }, { async: ",[65,1095,1096],{"class":137},"true",[65,1098,1099],{"class":71}," })\n",[65,1101,1102,1104,1107,1109,1112,1114,1116],{"class":67,"line":184},[65,1103,1081],{"class":71},[65,1105,1106],{"class":75},"then",[65,1108,237],{"class":71},[65,1110,1111],{"class":130},"zone",[65,1113,243],{"class":71},[65,1115,246],{"class":117},[65,1117,1118],{"class":71}," zone.zoneId)\n",[65,1120,1121],{"class":67,"line":200},[65,1122,210],{"emptyLinePlaceholder":209},[65,1124,1125],{"class":67,"line":206},[65,1126,1127],{"class":957},"// Per AWS, ACM certificate must be in the us-east-1 region.\n",[65,1129,1130,1132,1135,1137,1139,1142,1145,1147,1150],{"class":67,"line":213},[65,1131,118],{"class":117},[65,1133,1134],{"class":137}," eastRegion",[65,1136,124],{"class":117},[65,1138,486],{"class":117},[65,1140,1141],{"class":71}," aws.",[65,1143,1144],{"class":75},"Provider",[65,1146,192],{"class":71},[65,1148,1149],{"class":82},"'east'",[65,1151,1152],{"class":71},", {\n",[65,1154,1155],{"class":67,"line":226},[65,1156,1157],{"class":71},"    profile: aws.config.profile,\n",[65,1159,1160,1163,1166],{"class":67,"line":254},[65,1161,1162],{"class":71},"    region: ",[65,1164,1165],{"class":82},"'us-east-1'",[65,1167,780],{"class":71},[65,1169,1170],{"class":67,"line":259},[65,1171,1172],{"class":71},"})\n",[65,1174,1175],{"class":67,"line":264},[65,1176,210],{"emptyLinePlaceholder":209},[65,1178,1179,1181,1184,1186,1189,1191,1194,1196,1199,1201],{"class":67,"line":290},[65,1180,118],{"class":117},[65,1182,1183],{"class":137}," certificateConfig",[65,1185,134],{"class":117},[65,1187,1188],{"class":75}," aws",[65,1190,533],{"class":71},[65,1192,1193],{"class":75},"acm",[65,1195,533],{"class":71},[65,1197,1198],{"class":75},"CertificateArgs",[65,1200,124],{"class":117},[65,1202,151],{"class":71},[65,1204,1205,1208,1210],{"class":67,"line":304},[65,1206,1207],{"class":71},"    domainName: ",[65,1209,1090],{"class":82},[65,1211,780],{"class":71},[65,1213,1214,1217,1220],{"class":67,"line":316},[65,1215,1216],{"class":71},"    validationMethod: ",[65,1218,1219],{"class":82},"'DNS'",[65,1221,780],{"class":71},[65,1223,1224,1227,1230],{"class":67,"line":350},[65,1225,1226],{"class":71},"    subjectAlternativeNames: [",[65,1228,1229],{"class":82},"'www.yourdomain.com'",[65,1231,1232],{"class":71},"],\n",[65,1234,1235],{"class":67,"line":356},[65,1236,378],{"class":71},[65,1238,1239],{"class":67,"line":361},[65,1240,210],{"emptyLinePlaceholder":209},[65,1242,1243,1245,1248,1250,1252,1255,1258,1260,1263],{"class":67,"line":366},[65,1244,118],{"class":117},[65,1246,1247],{"class":137}," certificate",[65,1249,124],{"class":117},[65,1251,486],{"class":117},[65,1253,1254],{"class":71}," aws.acm.",[65,1256,1257],{"class":75},"Certificate",[65,1259,192],{"class":71},[65,1261,1262],{"class":82},"'certificate'",[65,1264,1265],{"class":71},", certificateConfig, {\n",[65,1267,1268],{"class":67,"line":375},[65,1269,1270],{"class":71},"    provider: eastRegion,\n",[65,1272,1273],{"class":67,"line":381},[65,1274,1172],{"class":71},[65,1276,1277],{"class":67,"line":386},[65,1278,210],{"emptyLinePlaceholder":209},[65,1280,1281,1283,1286,1288,1290,1293,1296],{"class":67,"line":443},[65,1282,118],{"class":117},[65,1284,1285],{"class":137}," certificateValidationDomain",[65,1287,124],{"class":117},[65,1289,486],{"class":117},[65,1291,1292],{"class":71}," aws.route53.",[65,1294,1295],{"class":75},"Record",[65,1297,301],{"class":71},[65,1299,1300,1303],{"class":67,"line":459},[65,1301,1302],{"class":82},"    `yourdomain.com-validation`",[65,1304,780],{"class":71},[65,1306,1307],{"class":67,"line":464},[65,1308,1016],{"class":71},[65,1310,1311,1314,1317],{"class":67,"line":480},[65,1312,1313],{"class":71},"        name: certificate.domainValidationOptions[",[65,1315,1316],{"class":137},"0",[65,1318,1319],{"class":71},"].resourceRecordName,\n",[65,1321,1322],{"class":67,"line":500},[65,1323,1324],{"class":71},"        zoneId: hostedZoneId,\n",[65,1326,1327,1330,1332],{"class":67,"line":505},[65,1328,1329],{"class":71},"        type: certificate.domainValidationOptions[",[65,1331,1316],{"class":137},[65,1333,1334],{"class":71},"].resourceRecordType,\n",[65,1336,1337,1340,1342],{"class":67,"line":510},[65,1338,1339],{"class":71},"        records: [certificate.domainValidationOptions[",[65,1341,1316],{"class":137},[65,1343,1344],{"class":71},"].resourceRecordValue],\n",[65,1346,1347,1350,1353,1356,1359],{"class":67,"line":518},[65,1348,1349],{"class":71},"        ttl: ",[65,1351,1352],{"class":137},"60",[65,1354,1355],{"class":117}," *",[65,1357,1358],{"class":137}," 10",[65,1360,780],{"class":71},[65,1362,1364],{"class":67,"line":1363},29,[65,1365,1046],{"class":71},[65,1367,1369],{"class":67,"line":1368},30,[65,1370,497],{"class":71},[65,1372,1374],{"class":67,"line":1373},31,[65,1375,210],{"emptyLinePlaceholder":209},[65,1377,1379,1381,1384,1386,1388,1390,1392],{"class":67,"line":1378},32,[65,1380,118],{"class":117},[65,1382,1383],{"class":137}," wwwCertificateValidationDomain",[65,1385,124],{"class":117},[65,1387,486],{"class":117},[65,1389,1292],{"class":71},[65,1391,1295],{"class":75},[65,1393,301],{"class":71},[65,1395,1397,1400],{"class":67,"line":1396},33,[65,1398,1399],{"class":82},"    `www.yourdomain.com-validation`",[65,1401,780],{"class":71},[65,1403,1405],{"class":67,"line":1404},34,[65,1406,1016],{"class":71},[65,1408,1410,1412,1415],{"class":67,"line":1409},35,[65,1411,1313],{"class":71},[65,1413,1414],{"class":137},"1",[65,1416,1319],{"class":71},[65,1418,1420],{"class":67,"line":1419},36,[65,1421,1324],{"class":71},[65,1423,1425,1427,1429],{"class":67,"line":1424},37,[65,1426,1329],{"class":71},[65,1428,1414],{"class":137},[65,1430,1334],{"class":71},[65,1432,1434,1436,1438],{"class":67,"line":1433},38,[65,1435,1339],{"class":71},[65,1437,1414],{"class":137},[65,1439,1344],{"class":71},[65,1441,1443,1445,1447,1449,1451],{"class":67,"line":1442},39,[65,1444,1349],{"class":71},[65,1446,1352],{"class":137},[65,1448,1355],{"class":117},[65,1450,1358],{"class":137},[65,1452,780],{"class":71},[65,1454,1456],{"class":67,"line":1455},40,[65,1457,1046],{"class":71},[65,1459,1461],{"class":67,"line":1460},41,[65,1462,497],{"class":71},[65,1464,1466],{"class":67,"line":1465},42,[65,1467,210],{"emptyLinePlaceholder":209},[65,1469,1471,1473,1476,1478,1480,1482,1485],{"class":67,"line":1470},43,[65,1472,118],{"class":117},[65,1474,1475],{"class":137}," certificateValidation",[65,1477,124],{"class":117},[65,1479,486],{"class":117},[65,1481,1254],{"class":71},[65,1483,1484],{"class":75},"CertificateValidation",[65,1486,301],{"class":71},[65,1488,1490,1493],{"class":67,"line":1489},44,[65,1491,1492],{"class":82},"    'certificateValidation'",[65,1494,780],{"class":71},[65,1496,1498],{"class":67,"line":1497},45,[65,1499,1016],{"class":71},[65,1501,1503],{"class":67,"line":1502},46,[65,1504,1505],{"class":71},"        certificateArn: certificate.arn,\n",[65,1507,1509],{"class":67,"line":1508},47,[65,1510,1511],{"class":71},"        validationRecordFqdns: [\n",[65,1513,1515],{"class":67,"line":1514},48,[65,1516,1517],{"class":71},"            certificateValidationDomain.fqdn,\n",[65,1519,1521],{"class":67,"line":1520},49,[65,1522,1523],{"class":71},"            wwwCertificateValidationDomain.fqdn,\n",[65,1525,1527],{"class":67,"line":1526},50,[65,1528,1529],{"class":71},"        ],\n",[65,1531,1533],{"class":67,"line":1532},51,[65,1534,1535],{"class":71},"    },\n",[65,1537,1539],{"class":67,"line":1538},52,[65,1540,1541],{"class":71},"    { provider: eastRegion }\n",[65,1543,1545],{"class":67,"line":1544},53,[65,1546,497],{"class":71},[65,1548,1550],{"class":67,"line":1549},54,[65,1551,210],{"emptyLinePlaceholder":209},[65,1553,1555,1558,1560],{"class":67,"line":1554},55,[65,1556,1557],{"class":71},"certificateArn ",[65,1559,586],{"class":117},[65,1561,1562],{"class":71}," certificateValidation.certificateArn\n",[15,1564,1565],{},"The next portion makes sure that your S3 bucket is not accessible to the public. It also sets up the your Cloudflare CDN that will mirror your S3 bucket and make your website accessible.",[55,1567,1569],{"className":57,"code":1568,"language":59,"meta":60,"style":60},"// Needed to set up S3 policies and make the bucket not public\nconst originAccessIdentity = new aws.cloudfront.OriginAccessIdentity(\n    'originAccessIdentity'\n)\n\n// distributionArgs configures the CloudFront distribution.\nconst distributionArgs: aws.cloudfront.DistributionArgs = {\n    enabled: true,\n    aliases: ['yourdomain.com', 'www.yourdomain.com'],\n    origins: [\n        {\n            originId: bucket.arn,\n            domainName: bucket.bucketRegionalDomainName,\n            s3OriginConfig: {\n                originAccessIdentity:\n                    originAccessIdentity.cloudfrontAccessIdentityPath,\n            },\n        },\n    ],\n\n    defaultRootObject: 'index.html',\n\n    // A CloudFront distribution can configure different cache behaviors based on the request path.\n    // Here we just specify a single, default cache behavior which is just read-only requests to S3.\n    defaultCacheBehavior: {\n        targetOriginId: bucket.arn,\n\n        viewerProtocolPolicy: 'redirect-to-https',\n        allowedMethods: ['GET', 'HEAD', 'OPTIONS'],\n        cachedMethods: ['GET', 'HEAD', 'OPTIONS'],\n\n        forwardedValues: {\n            cookies: { forward: 'none' },\n            queryString: false,\n        },\n\n        minTtl: 0,\n        defaultTtl: 10 * 60,\n        maxTtl: 10 * 60,\n    },\n\n    // \"All\" is the most broad distribution, and also the most expensive.\n    // \"100\" is the least broad, and also the least expensive.\n    priceClass: 'PriceClass_100',\n\n    restrictions: {\n        geoRestriction: {\n            restrictionType: 'none',\n        },\n    },\n\n    viewerCertificate: {\n        acmCertificateArn: certificateArn, // Per AWS, ACM certificate must be in the us-east-1 region.\n        sslSupportMethod: 'sni-only',\n    },\n}\n\nconst cdn = new aws.cloudfront.Distribution('cdn', distributionArgs)\n",[62,1570,1571,1576,1595,1600,1604,1608,1613,1638,1647,1660,1665,1669,1674,1679,1684,1689,1694,1699,1703,1708,1712,1721,1725,1730,1735,1740,1745,1749,1759,1779,1796,1800,1805,1816,1826,1830,1834,1843,1858,1871,1875,1879,1884,1889,1899,1903,1908,1913,1922,1926,1930,1934,1939,1946,1956,1960,1965,1970],{"__ignoreMap":60},[65,1572,1573],{"class":67,"line":68},[65,1574,1575],{"class":957},"// Needed to set up S3 policies and make the bucket not public\n",[65,1577,1578,1580,1583,1585,1587,1590,1593],{"class":67,"line":154},[65,1579,118],{"class":117},[65,1581,1582],{"class":137}," originAccessIdentity",[65,1584,124],{"class":117},[65,1586,486],{"class":117},[65,1588,1589],{"class":71}," aws.cloudfront.",[65,1591,1592],{"class":75},"OriginAccessIdentity",[65,1594,301],{"class":71},[65,1596,1597],{"class":67,"line":184},[65,1598,1599],{"class":82},"    'originAccessIdentity'\n",[65,1601,1602],{"class":67,"line":200},[65,1603,497],{"class":71},[65,1605,1606],{"class":67,"line":206},[65,1607,210],{"emptyLinePlaceholder":209},[65,1609,1610],{"class":67,"line":213},[65,1611,1612],{"class":957},"// distributionArgs configures the CloudFront distribution.\n",[65,1614,1615,1617,1620,1622,1624,1626,1629,1631,1634,1636],{"class":67,"line":226},[65,1616,118],{"class":117},[65,1618,1619],{"class":137}," distributionArgs",[65,1621,134],{"class":117},[65,1623,1188],{"class":75},[65,1625,533],{"class":71},[65,1627,1628],{"class":75},"cloudfront",[65,1630,533],{"class":71},[65,1632,1633],{"class":75},"DistributionArgs",[65,1635,124],{"class":117},[65,1637,151],{"class":71},[65,1639,1640,1643,1645],{"class":67,"line":254},[65,1641,1642],{"class":71},"    enabled: ",[65,1644,1096],{"class":137},[65,1646,780],{"class":71},[65,1648,1649,1652,1654,1656,1658],{"class":67,"line":259},[65,1650,1651],{"class":71},"    aliases: [",[65,1653,1090],{"class":82},[65,1655,330],{"class":71},[65,1657,1229],{"class":82},[65,1659,1232],{"class":71},[65,1661,1662],{"class":67,"line":264},[65,1663,1664],{"class":71},"    origins: [\n",[65,1666,1667],{"class":67,"line":290},[65,1668,793],{"class":71},[65,1670,1671],{"class":67,"line":304},[65,1672,1673],{"class":71},"            originId: bucket.arn,\n",[65,1675,1676],{"class":67,"line":316},[65,1677,1678],{"class":71},"            domainName: bucket.bucketRegionalDomainName,\n",[65,1680,1681],{"class":67,"line":350},[65,1682,1683],{"class":71},"            s3OriginConfig: {\n",[65,1685,1686],{"class":67,"line":356},[65,1687,1688],{"class":71},"                originAccessIdentity:\n",[65,1690,1691],{"class":67,"line":361},[65,1692,1693],{"class":71},"                    originAccessIdentity.cloudfrontAccessIdentityPath,\n",[65,1695,1696],{"class":67,"line":366},[65,1697,1698],{"class":71},"            },\n",[65,1700,1701],{"class":67,"line":375},[65,1702,1041],{"class":71},[65,1704,1705],{"class":67,"line":381},[65,1706,1707],{"class":71},"    ],\n",[65,1709,1710],{"class":67,"line":386},[65,1711,210],{"emptyLinePlaceholder":209},[65,1713,1714,1717,1719],{"class":67,"line":443},[65,1715,1716],{"class":71},"    defaultRootObject: ",[65,1718,1034],{"class":82},[65,1720,780],{"class":71},[65,1722,1723],{"class":67,"line":459},[65,1724,210],{"emptyLinePlaceholder":209},[65,1726,1727],{"class":67,"line":464},[65,1728,1729],{"class":957},"    // A CloudFront distribution can configure different cache behaviors based on the request path.\n",[65,1731,1732],{"class":67,"line":480},[65,1733,1734],{"class":957},"    // Here we just specify a single, default cache behavior which is just read-only requests to S3.\n",[65,1736,1737],{"class":67,"line":500},[65,1738,1739],{"class":71},"    defaultCacheBehavior: {\n",[65,1741,1742],{"class":67,"line":505},[65,1743,1744],{"class":71},"        targetOriginId: bucket.arn,\n",[65,1746,1747],{"class":67,"line":510},[65,1748,210],{"emptyLinePlaceholder":209},[65,1750,1751,1754,1757],{"class":67,"line":518},[65,1752,1753],{"class":71},"        viewerProtocolPolicy: ",[65,1755,1756],{"class":82},"'redirect-to-https'",[65,1758,780],{"class":71},[65,1760,1761,1764,1767,1769,1772,1774,1777],{"class":67,"line":1363},[65,1762,1763],{"class":71},"        allowedMethods: [",[65,1765,1766],{"class":82},"'GET'",[65,1768,330],{"class":71},[65,1770,1771],{"class":82},"'HEAD'",[65,1773,330],{"class":71},[65,1775,1776],{"class":82},"'OPTIONS'",[65,1778,1232],{"class":71},[65,1780,1781,1784,1786,1788,1790,1792,1794],{"class":67,"line":1368},[65,1782,1783],{"class":71},"        cachedMethods: [",[65,1785,1766],{"class":82},[65,1787,330],{"class":71},[65,1789,1771],{"class":82},[65,1791,330],{"class":71},[65,1793,1776],{"class":82},[65,1795,1232],{"class":71},[65,1797,1798],{"class":67,"line":1373},[65,1799,210],{"emptyLinePlaceholder":209},[65,1801,1802],{"class":67,"line":1378},[65,1803,1804],{"class":71},"        forwardedValues: {\n",[65,1806,1807,1810,1813],{"class":67,"line":1396},[65,1808,1809],{"class":71},"            cookies: { forward: ",[65,1811,1812],{"class":82},"'none'",[65,1814,1815],{"class":71}," },\n",[65,1817,1818,1821,1824],{"class":67,"line":1404},[65,1819,1820],{"class":71},"            queryString: ",[65,1822,1823],{"class":137},"false",[65,1825,780],{"class":71},[65,1827,1828],{"class":67,"line":1409},[65,1829,1041],{"class":71},[65,1831,1832],{"class":67,"line":1419},[65,1833,210],{"emptyLinePlaceholder":209},[65,1835,1836,1839,1841],{"class":67,"line":1424},[65,1837,1838],{"class":71},"        minTtl: ",[65,1840,1316],{"class":137},[65,1842,780],{"class":71},[65,1844,1845,1848,1851,1853,1856],{"class":67,"line":1433},[65,1846,1847],{"class":71},"        defaultTtl: ",[65,1849,1850],{"class":137},"10",[65,1852,1355],{"class":117},[65,1854,1855],{"class":137}," 60",[65,1857,780],{"class":71},[65,1859,1860,1863,1865,1867,1869],{"class":67,"line":1442},[65,1861,1862],{"class":71},"        maxTtl: ",[65,1864,1850],{"class":137},[65,1866,1355],{"class":117},[65,1868,1855],{"class":137},[65,1870,780],{"class":71},[65,1872,1873],{"class":67,"line":1455},[65,1874,1535],{"class":71},[65,1876,1877],{"class":67,"line":1460},[65,1878,210],{"emptyLinePlaceholder":209},[65,1880,1881],{"class":67,"line":1465},[65,1882,1883],{"class":957},"    // \"All\" is the most broad distribution, and also the most expensive.\n",[65,1885,1886],{"class":67,"line":1470},[65,1887,1888],{"class":957},"    // \"100\" is the least broad, and also the least expensive.\n",[65,1890,1891,1894,1897],{"class":67,"line":1489},[65,1892,1893],{"class":71},"    priceClass: ",[65,1895,1896],{"class":82},"'PriceClass_100'",[65,1898,780],{"class":71},[65,1900,1901],{"class":67,"line":1497},[65,1902,210],{"emptyLinePlaceholder":209},[65,1904,1905],{"class":67,"line":1502},[65,1906,1907],{"class":71},"    restrictions: {\n",[65,1909,1910],{"class":67,"line":1508},[65,1911,1912],{"class":71},"        geoRestriction: {\n",[65,1914,1915,1918,1920],{"class":67,"line":1514},[65,1916,1917],{"class":71},"            restrictionType: ",[65,1919,1812],{"class":82},[65,1921,780],{"class":71},[65,1923,1924],{"class":67,"line":1520},[65,1925,1041],{"class":71},[65,1927,1928],{"class":67,"line":1526},[65,1929,1535],{"class":71},[65,1931,1932],{"class":67,"line":1532},[65,1933,210],{"emptyLinePlaceholder":209},[65,1935,1936],{"class":67,"line":1538},[65,1937,1938],{"class":71},"    viewerCertificate: {\n",[65,1940,1941,1944],{"class":67,"line":1544},[65,1942,1943],{"class":71},"        acmCertificateArn: certificateArn, ",[65,1945,1127],{"class":957},[65,1947,1948,1951,1954],{"class":67,"line":1549},[65,1949,1950],{"class":71},"        sslSupportMethod: ",[65,1952,1953],{"class":82},"'sni-only'",[65,1955,780],{"class":71},[65,1957,1958],{"class":67,"line":1554},[65,1959,1535],{"class":71},[65,1961,1963],{"class":67,"line":1962},56,[65,1964,378],{"class":71},[65,1966,1968],{"class":67,"line":1967},57,[65,1969,210],{"emptyLinePlaceholder":209},[65,1971,1973,1975,1978,1980,1982,1984,1987,1989,1992],{"class":67,"line":1972},58,[65,1974,118],{"class":117},[65,1976,1977],{"class":137}," cdn",[65,1979,124],{"class":117},[65,1981,486],{"class":117},[65,1983,1589],{"class":71},[65,1985,1986],{"class":75},"Distribution",[65,1988,192],{"class":71},[65,1990,1991],{"class":82},"'cdn'",[65,1993,1994],{"class":71},", distributionArgs)\n",[15,1996,1997],{},"The final lines of the index.ts file are the ones that actually create the Alias records in Route 53 and set the bucket's origin policy.",[55,1999,2001],{"className":57,"code":2000,"language":59,"meta":60,"style":60},"const aliasRecord = new aws.route53.Record('yourdomain.com-alias', {\n    name: 'yourdomain.com',\n    zoneId: hostedZoneId,\n    type: 'A',\n    aliases: [\n        {\n            name: cdn.domainName,\n            zoneId: cdn.hostedZoneId,\n            evaluateTargetHealth: true,\n        },\n    ],\n})\n\nconst wwwAliasRecord = new aws.route53.Record(`yourdomain.com-www-alias`, {\n    name: `www.yourdomain.com`,\n    zoneId: hostedZoneId,\n    type: 'A',\n    aliases: [\n        {\n            name: cdn.domainName,\n            zoneId: cdn.hostedZoneId,\n            evaluateTargetHealth: true,\n        },\n    ],\n})\n\nconst bucketPolicy = new aws.s3.BucketPolicy('bucketPolicy', {\n    bucket: bucket.id, // refer to the bucket created earlier\n    policy: pulumi.jsonStringify({\n        Version: '2012-10-17',\n        Statement: [\n            {\n                Effect: 'Allow',\n                Principal: {\n                    AWS: originAccessIdentity.iamArn,\n                }, // Only allow Cloudfront read access.\n                Action: ['s3:GetObject'],\n                Resource: [pulumi.interpolate`${bucket.arn}/*`], // Give Cloudfront access to the entire bucket.\n            },\n        ],\n    }),\n})\n",[62,2002,2003,2025,2034,2039,2049,2054,2058,2063,2068,2077,2081,2085,2089,2093,2115,2124,2128,2136,2140,2144,2148,2152,2160,2164,2168,2172,2176,2199,2207,2218,2228,2233,2238,2248,2253,2258,2266,2276,2304,2308,2312,2317],{"__ignoreMap":60},[65,2004,2005,2007,2010,2012,2014,2016,2018,2020,2023],{"class":67,"line":68},[65,2006,118],{"class":117},[65,2008,2009],{"class":137}," aliasRecord",[65,2011,124],{"class":117},[65,2013,486],{"class":117},[65,2015,1292],{"class":71},[65,2017,1295],{"class":75},[65,2019,192],{"class":71},[65,2021,2022],{"class":82},"'yourdomain.com-alias'",[65,2024,1152],{"class":71},[65,2026,2027,2030,2032],{"class":67,"line":154},[65,2028,2029],{"class":71},"    name: ",[65,2031,1090],{"class":82},[65,2033,780],{"class":71},[65,2035,2036],{"class":67,"line":184},[65,2037,2038],{"class":71},"    zoneId: hostedZoneId,\n",[65,2040,2041,2044,2047],{"class":67,"line":200},[65,2042,2043],{"class":71},"    type: ",[65,2045,2046],{"class":82},"'A'",[65,2048,780],{"class":71},[65,2050,2051],{"class":67,"line":206},[65,2052,2053],{"class":71},"    aliases: [\n",[65,2055,2056],{"class":67,"line":213},[65,2057,793],{"class":71},[65,2059,2060],{"class":67,"line":226},[65,2061,2062],{"class":71},"            name: cdn.domainName,\n",[65,2064,2065],{"class":67,"line":254},[65,2066,2067],{"class":71},"            zoneId: cdn.hostedZoneId,\n",[65,2069,2070,2073,2075],{"class":67,"line":259},[65,2071,2072],{"class":71},"            evaluateTargetHealth: ",[65,2074,1096],{"class":137},[65,2076,780],{"class":71},[65,2078,2079],{"class":67,"line":264},[65,2080,1041],{"class":71},[65,2082,2083],{"class":67,"line":290},[65,2084,1707],{"class":71},[65,2086,2087],{"class":67,"line":304},[65,2088,1172],{"class":71},[65,2090,2091],{"class":67,"line":316},[65,2092,210],{"emptyLinePlaceholder":209},[65,2094,2095,2097,2100,2102,2104,2106,2108,2110,2113],{"class":67,"line":350},[65,2096,118],{"class":117},[65,2098,2099],{"class":137}," wwwAliasRecord",[65,2101,124],{"class":117},[65,2103,486],{"class":117},[65,2105,1292],{"class":71},[65,2107,1295],{"class":75},[65,2109,192],{"class":71},[65,2111,2112],{"class":82},"`yourdomain.com-www-alias`",[65,2114,1152],{"class":71},[65,2116,2117,2119,2122],{"class":67,"line":356},[65,2118,2029],{"class":71},[65,2120,2121],{"class":82},"`www.yourdomain.com`",[65,2123,780],{"class":71},[65,2125,2126],{"class":67,"line":361},[65,2127,2038],{"class":71},[65,2129,2130,2132,2134],{"class":67,"line":366},[65,2131,2043],{"class":71},[65,2133,2046],{"class":82},[65,2135,780],{"class":71},[65,2137,2138],{"class":67,"line":375},[65,2139,2053],{"class":71},[65,2141,2142],{"class":67,"line":381},[65,2143,793],{"class":71},[65,2145,2146],{"class":67,"line":386},[65,2147,2062],{"class":71},[65,2149,2150],{"class":67,"line":443},[65,2151,2067],{"class":71},[65,2153,2154,2156,2158],{"class":67,"line":459},[65,2155,2072],{"class":71},[65,2157,1096],{"class":137},[65,2159,780],{"class":71},[65,2161,2162],{"class":67,"line":464},[65,2163,1041],{"class":71},[65,2165,2166],{"class":67,"line":480},[65,2167,1707],{"class":71},[65,2169,2170],{"class":67,"line":500},[65,2171,1172],{"class":71},[65,2173,2174],{"class":67,"line":505},[65,2175,210],{"emptyLinePlaceholder":209},[65,2177,2178,2180,2183,2185,2187,2189,2192,2194,2197],{"class":67,"line":510},[65,2179,118],{"class":117},[65,2181,2182],{"class":137}," bucketPolicy",[65,2184,124],{"class":117},[65,2186,486],{"class":117},[65,2188,972],{"class":71},[65,2190,2191],{"class":75},"BucketPolicy",[65,2193,192],{"class":71},[65,2195,2196],{"class":82},"'bucketPolicy'",[65,2198,1152],{"class":71},[65,2200,2201,2204],{"class":67,"line":518},[65,2202,2203],{"class":71},"    bucket: bucket.id, ",[65,2205,2206],{"class":957},"// refer to the bucket created earlier\n",[65,2208,2209,2212,2215],{"class":67,"line":1363},[65,2210,2211],{"class":71},"    policy: pulumi.",[65,2213,2214],{"class":75},"jsonStringify",[65,2216,2217],{"class":71},"({\n",[65,2219,2220,2223,2226],{"class":67,"line":1368},[65,2221,2222],{"class":71},"        Version: ",[65,2224,2225],{"class":82},"'2012-10-17'",[65,2227,780],{"class":71},[65,2229,2230],{"class":67,"line":1373},[65,2231,2232],{"class":71},"        Statement: [\n",[65,2234,2235],{"class":67,"line":1378},[65,2236,2237],{"class":71},"            {\n",[65,2239,2240,2243,2246],{"class":67,"line":1396},[65,2241,2242],{"class":71},"                Effect: ",[65,2244,2245],{"class":82},"'Allow'",[65,2247,780],{"class":71},[65,2249,2250],{"class":67,"line":1404},[65,2251,2252],{"class":71},"                Principal: {\n",[65,2254,2255],{"class":67,"line":1409},[65,2256,2257],{"class":71},"                    AWS: originAccessIdentity.iamArn,\n",[65,2259,2260,2263],{"class":67,"line":1419},[65,2261,2262],{"class":71},"                }, ",[65,2264,2265],{"class":957},"// Only allow Cloudfront read access.\n",[65,2267,2268,2271,2274],{"class":67,"line":1424},[65,2269,2270],{"class":71},"                Action: [",[65,2272,2273],{"class":82},"'s3:GetObject'",[65,2275,1232],{"class":71},[65,2277,2278,2281,2284,2287,2290,2292,2295,2298,2301],{"class":67,"line":1433},[65,2279,2280],{"class":71},"                Resource: [pulumi.",[65,2282,2283],{"class":75},"interpolate",[65,2285,2286],{"class":82},"`${",[65,2288,2289],{"class":71},"bucket",[65,2291,533],{"class":82},[65,2293,2294],{"class":71},"arn",[65,2296,2297],{"class":82},"}/*`",[65,2299,2300],{"class":71},"], ",[65,2302,2303],{"class":957},"// Give Cloudfront access to the entire bucket.\n",[65,2305,2306],{"class":67,"line":1442},[65,2307,1698],{"class":71},[65,2309,2310],{"class":67,"line":1455},[65,2311,1529],{"class":71},[65,2313,2314],{"class":67,"line":1460},[65,2315,2316],{"class":71},"    }),\n",[65,2318,2319],{"class":67,"line":1465},[65,2320,1172],{"class":71},[15,2322,2323],{},"And that's the whole file! 🎉 We just need to run the following command to set up the infrastructure we just wrote in code.",[55,2325,2327],{"className":930,"code":2326,"language":932,"meta":60,"style":60},"pulumi up -y\n",[62,2328,2329],{"__ignoreMap":60},[65,2330,2331],{"class":67,"line":68},[65,2332,2326],{},[15,2334,2335],{},"Note that we still actually need to sync the static files in our S3 bucket. There are multiple ways to do this (including the aws-cli command), but for now, we can just upload the files to the bucket via the S3 bucket.",[670,2337,2338],{},"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 .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":60,"searchDepth":154,"depth":154,"links":2340},[2341,2342,2343],{"id":716,"depth":154,"text":717},{"id":744,"depth":154,"text":745},{"id":917,"depth":154,"text":918},"2025-03-31T00:00:00.000Z","While updating my portfolio, I stumbled across a post on dev.to about Pulumi. I found Pulumi interesting because how it makes infrastructure as code more approachable with libraries in popular programming languages (TypeScript, Java, Go, etc.). Hopefully, I'd also be able to execute this script in any of my future apps for easy server provisioning.",{},"/blog/pulumi-static-site-deployment",{"title":693,"description":2345},"blog/pulumi-static-site-deployment",[2351,2352,2353,2354],"pulumi","iac","nuxt","aws","yjb_vyQMPXEUM3wnjEtEtL4Ez0MT5xJ1JdO76aHzX_E",1769573871096]