<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rebeca Murillo</title>
    <description>The latest articles on DEV Community by Rebeca Murillo (@rebecamurillo).</description>
    <link>https://dev.to/rebecamurillo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1207767%2Fa7b4577f-0587-403d-905d-c015341808ea.jpeg</url>
      <title>DEV Community: Rebeca Murillo</title>
      <link>https://dev.to/rebecamurillo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rebecamurillo"/>
    <language>en</language>
    <item>
      <title>CI/CI deploy a static website to AWS S3 bucket through Github Actions</title>
      <dc:creator>Rebeca Murillo</dc:creator>
      <pubDate>Fri, 22 Dec 2023 10:00:38 +0000</pubDate>
      <link>https://dev.to/rebecamurillo/cici-deploy-a-static-website-to-aws-s3-bucket-through-github-actions-2g97</link>
      <guid>https://dev.to/rebecamurillo/cici-deploy-a-static-website-to-aws-s3-bucket-through-github-actions-2g97</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Welcome to this tutorial where we'll delve into the process of setting up continuous integration and continuous delivery (CI/CD) for deploying a static website to an Amazon S3 bucket directly from your GitHub repository. We'll walk through the process of setting up GitHub Actions in your project, configuring your AWS account, and ensuring that your website is running smoothly after each deployment. &lt;/p&gt;

&lt;p&gt;This configuration might take some time to setup, but it will help you save time afterwards ! Your website will be updated automatically with one click, just by running the Github workflow on your main branch. &lt;/p&gt;

&lt;p&gt;This tutorial breaks down in three steps : &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Github Actions workflow&lt;/li&gt;
&lt;li&gt;Setub AWS Role to connect with Github Actions&lt;/li&gt;
&lt;li&gt;Setup Github Actions secrets and run the workflow&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;To follow along this tutorial, you will need to setup in advance the following : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A GitHub repository with your static website source code. Check my tutorial on starting a &lt;a href="https://dev.to/en/blog/astro-starter-blog-i18n"&gt;multilingual i18n blog with Astro&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;A AWS account with an S3 bucket hosting your static website. You can learn this with my tutorial on &lt;a href="https://dev.to/en/blog/static-website-aws-s3-cloudfront"&gt;hosting a static website in AWS S3 bucket&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A CloudFront distribution for the cache and CDN. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Through this guide, you'll gain an in-depth understanding of building, quality checking, and deploying your static website, and finally checking the production site. Let's get started on this exciting journey of automating your website deployment!&lt;/p&gt;

&lt;h2&gt;
  
  
  Github Actions workflow
&lt;/h2&gt;

&lt;p&gt;To kickstart the process, we'll set up GitHub Actions in the project. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a file under &lt;code&gt;.github/workflows/build-deploy.yml&lt;/code&gt; in your project directory.&lt;/li&gt;
&lt;li&gt;Copy the following content, adapt according to your needs.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and deploy to S3 bucket&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workflow_dispatch&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout Repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Output to Dist&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Build Artifacts&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-artifacts&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;

  &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout Repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download Build Artifacts&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-artifacts&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.10"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run localhost server&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cd dist ; ls -la ;&lt;/span&gt;
          &lt;span class="s"&gt;python -m http.server 8080 &amp;amp;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Wait for Local Server to Start&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sleep &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check homepage status in localhost&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;URLs=("http://localhost:8080/" "http://localhost:8080/en/" "http://localhost:8080/es/" "http://localhost:8080/fr/")&lt;/span&gt;
          &lt;span class="s"&gt;for url in "${URLs[@]}"; do&lt;/span&gt;
              &lt;span class="s"&gt;response_code=$(curl -w "%{http_code}" -I "$url" -o /dev/null)&lt;/span&gt;
              &lt;span class="s"&gt;echo -e "e HTTP Status Code for $url: $response_code"&lt;/span&gt;
              &lt;span class="s"&gt;echo "HTTP Status Code for $url: $response_code"&lt;/span&gt;

              &lt;span class="s"&gt;if [ "$response_code" -ne 200 ]; then&lt;/span&gt;
                  &lt;span class="s"&gt;echo -e "::error::Error: Unexpected status code for $url"&lt;/span&gt;
                  &lt;span class="s"&gt;exit 1&lt;/span&gt;
              &lt;span class="s"&gt;fi&lt;/span&gt;
          &lt;span class="s"&gt;done&lt;/span&gt;

  &lt;span class="na"&gt;aws_deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;quality&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download Build Artifacts&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-artifacts&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_REGION }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Empty S3 Bucket&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 rm s3://${{ secrets.AWS_S3_BUCKET }} --recursive&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to S3 bucket&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;ls -la dist/&lt;/span&gt;
          &lt;span class="s"&gt;aws s3 cp dist/. s3://${{ secrets.AWS_S3_BUCKET }} --recursive&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clean CloudFront Cache&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"&lt;/span&gt;

  &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws_deploy&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout Repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check homepage status @ https://mywebsite&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;URLs=("https://rebeca.murillo.link/" "https://rebeca.murillo.link/en/" "https://rebeca.murillo.link/es/" "https://rebeca.murillo.link/fr/")&lt;/span&gt;
          &lt;span class="s"&gt;for url in "${URLs[@]}"; do&lt;/span&gt;
              &lt;span class="s"&gt;response_code=$(curl -w "%{http_code}" -I "$url" -o /dev/null)&lt;/span&gt;
              &lt;span class="s"&gt;echo -e "e HTTP Status Code for $url: $response_code"&lt;/span&gt;
              &lt;span class="s"&gt;echo "HTTP Status Code for $url: $response_code"&lt;/span&gt;

              &lt;span class="s"&gt;if [ "$response_code" -ne 200 ]; then&lt;/span&gt;
                  &lt;span class="s"&gt;echo -e "::error::Error: Unexpected status code for $url"&lt;/span&gt;
                  &lt;span class="s"&gt;exit 1&lt;/span&gt;
              &lt;span class="s"&gt;fi&lt;/span&gt;
          &lt;span class="s"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow can be triggered by a manual run or you can customize it to run on conditions like a branch push on the main branch. Refer to &lt;a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows" rel="noopener noreferrer"&gt;Github Actions Events that trigger workflows documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The workflow is partitioned into four different jobs: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build &lt;/li&gt;
&lt;li&gt;Quality check on localhost &lt;/li&gt;
&lt;li&gt;S3 bucket deployment &lt;/li&gt;
&lt;li&gt;Checking the production site &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's go in detail into each job.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that the permissions is mandatory for aws-actions/configure-aws-credentials to authenticate !&lt;/p&gt;


&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
 &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. Build Job
&lt;/h3&gt;

&lt;p&gt;The first step is to build the static website source, following your website configuration and framework. In this tutorial, the project is built with the Astro Framework, so we run run a script to install Node dependencies with Node.js and then build the source code. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup environment with Nodejs and install dependencies with &lt;code&gt;npm install&lt;/code&gt;, with &lt;a href="https://github.com/actions/setup-node" rel="noopener noreferrer"&gt;Github Actions setup Node&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Build the static website final sources with command &lt;code&gt;npm run build&lt;/code&gt;, by default Astro framework saves the built sources in the &lt;code&gt;dist/&lt;/code&gt; folder. &lt;/li&gt;
&lt;li&gt;The content of the build destination folder folder needs is saved and transferred to the following jobs in the workflow. We do this with the Github actions &lt;a href="https://github.com/actions/upload-artifact" rel="noopener noreferrer"&gt;actions/upload-artifact&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The subsequent jobs will be running the &lt;a href="https://github.com/actions/download-artifact" rel="noopener noreferrer"&gt;Github Actions Download Artifact&lt;/a&gt;, in order to retrieve the &lt;code&gt;dist/&lt;/code&gt; folder content. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Quality Check on localhost
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;In this step you can add your project's test command, if you have one. Like for example &lt;code&gt;npm run tests&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second job involves running a quality check. For our static website example, the quality check consists on hosting the website locally and calling the homepage, or any other important pages. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The source files from the &lt;code&gt;dist/&lt;/code&gt; folder from our first job are used, retrieved with &lt;a href="https://github.com/actions/download-artifact" rel="noopener noreferrer"&gt;Github Actions Download Artifact&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run a local host with the static website content with &lt;a href="https://github.com/actions/setup-python" rel="noopener noreferrer"&gt;Python setup Github Actions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Check some of the website pages are functioning as expected. If any of the pages are not responding properly (200 status), the &lt;code&gt;exit 1&lt;/code&gt; is triggered, causing the GitHub workflow to fail. This will prevent the deployment of the source code in our S3 bucket. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. S3 Bucket Deployment
&lt;/h3&gt;

&lt;p&gt;After checking that the source generated code is running correctly, the third job will be deploying the source code to the AWS S3 bucket. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The source files from the &lt;code&gt;dist/&lt;/code&gt; folder from our first job are used, retrieved with &lt;a href="https://github.com/actions/download-artifact" rel="noopener noreferrer"&gt;Github Actions Download Artifact&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/aws-actions/configure-aws-credentials" rel="noopener noreferrer"&gt;AWS configure-aws-credentials Github Action&lt;/a&gt; allows the connection to the AWS S3 bucket through an AWS Role. &lt;em&gt;The configuration of this role is explained in the next chapter&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The AWS bucket hosting the static website is updated by first deleting the old content, and then uploading the updated source files.&lt;/li&gt;
&lt;li&gt;Finally the CloudFront cache is cleaned, as it may not immediately reflect the website changes. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Checking the Production Site
&lt;/h3&gt;

&lt;p&gt;The final job repeats the quality check from the second job, but this time, on the final website URL. I've set it up to check four pages, the homepage in three different languages, to ensure that the site is up and running after deployment. &lt;/p&gt;

&lt;p&gt;In the next section, we will look into AWS configurations necessary for this CI/CD setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setub AWS Role to connect with Github Actions
&lt;/h2&gt;

&lt;p&gt;Moving on, we are now going to set up our AWS account to connect it with GitHub Actions. To accomplish this, we need to create a role in AWS. This role will be used to configure the credentials in the GitHub Actions workflow.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Refer to the AWS documentation for more details about this process : &lt;a href="https://aws.amazon.com/fr/blogs/security/use-iam-roles-to-connect-github-actions-to-actions-in-aws/" rel="noopener noreferrer"&gt;Use IAM roles to connect GitHub Actions to actions in AWS&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. Create an OIDC provider for Github
&lt;/h3&gt;

&lt;p&gt;To start off, we need to create an identity provider for GitHub. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the AWS Console, go to Identity and Access Management (IAM), under Access Management &amp;gt; Identity Providers&lt;/li&gt;
&lt;li&gt;Click on 'Add a new provider' : 

&lt;ul&gt;
&lt;li&gt;provider type :  'Open ID Connect Provider' &lt;/li&gt;
&lt;li&gt;provider URL :&lt;code&gt;token.actions.githubusercontent.com&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Audience : &lt;code&gt;sts.amazonaws.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Once we have our identity provider established, we can proceed to create the role. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create the AWS Role
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;In the AWS Console, go to Identity and Access Management (IAM), under Access Management  &amp;gt;  'Roles'&lt;/li&gt;
&lt;li&gt;Click to create a "New role" : 

&lt;ul&gt;
&lt;li&gt;Entity type: Web identity. &lt;/li&gt;
&lt;li&gt;Select the identity provider we created in the previous step. The audience will be the default one. &lt;/li&gt;
&lt;li&gt;The GitHub organization is essentially your GitHub account where the project is hosted. &lt;/li&gt;
&lt;li&gt;The name of your GitHub repository&lt;/li&gt;
&lt;li&gt;The branch in your Github repository that will be allowed to run the Github Actions workflow&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;In the Permissions Policies step, setup the rules according to the actions that will be executed by the Github Actions workflow. For this tutorial, the permissions required are : 

&lt;ul&gt;
&lt;li&gt;AWS S3 bucket management: to delete and upload content in the bucket&lt;/li&gt;
&lt;li&gt;AWS Cloudfront management: to reset the CDN cache
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Save the new role&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;In the trust policy, you can verify important information regarding your Github repository permissions and the concerned branch. You can adapt this to your use case. &lt;br&gt;
Upon creating the role, open it to copy the ARN (Amazon Resource Name), which is needed in the next step for setting up the secrets in GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Github Actions secrets and run the workflow
&lt;/h2&gt;

&lt;p&gt;Back in your GitHub project, set up the required secrets for the workflow.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your GitHub project settings,&lt;/li&gt;
&lt;li&gt;Then to 'Secrets and Variables' under 'Actions'&lt;/li&gt;
&lt;li&gt;Finally create a secret for each variable with 'Create a new repository secret'.  In this tutorial, the secrets used in the workflow include: 

&lt;ul&gt;
&lt;li&gt;AWS_ROLE_ARN: the ARN from the AWS role created in the previous step&lt;/li&gt;
&lt;li&gt;AWS_REGION: the AWS region for the bucket&lt;/li&gt;
&lt;li&gt;AWS_S3_BUCKET: AWS S3 bucket name&lt;/li&gt;
&lt;li&gt;AWS_CLOUDFRONT_DISTRIBUTION_ID: the AWS CloudFront distribution ID &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With all these configurations in place, your AWS account is now set up and ready to be connected with your GitHub Actions.&lt;/p&gt;

&lt;p&gt;To run the workflow, navigate to the 'Actions' tab in the GitHub repository, select the workflow and run it from the corresponding branch. &lt;/p&gt;

&lt;p&gt;Once the workflow has started, you can monitor its progress. It will sequentially execute the four jobs we outlined earlier: build, quality check on localhost, S3 bucket deployment, and checking the production site. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that choosing the wrong branch will result the workflow to return errors. The branch must be allowed in the AWS role policies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I hope this tutorial gives you a clear understanding of how the AWS role and GitHub Actions work together to ensure secure and controlled deployments of your website. The process is now fully automated, and the permissions are set up to prevent unauthorized deployments, ensuring that your website remains secure and updated with only one click !&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>github</category>
      <category>s3</category>
    </item>
    <item>
      <title>Host a static website in AWS S3 bucket through Cloudfront CDN</title>
      <dc:creator>Rebeca Murillo</dc:creator>
      <pubDate>Thu, 16 Nov 2023 18:09:23 +0000</pubDate>
      <link>https://dev.to/rebecamurillo/host-a-static-website-in-aws-s3-bucket-through-cloudfront-cdn-4cpl</link>
      <guid>https://dev.to/rebecamurillo/host-a-static-website-in-aws-s3-bucket-through-cloudfront-cdn-4cpl</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In today's digital era, having a well-structured, functional website is a must for any business or individual seeking to maintain an online presence.  In this blog, we'll walk you through the process of hosting a static website on an Amazon S3 bucket, and using CloudFront for the Content Delivery Network (CDN). Let's get started.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This guide assumes you have a basic understanding of AWS and have the source code for your website ready to go. &lt;/p&gt;

&lt;p&gt;We'll be building the source code with Astro framework. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why did I choose to host my website on an S3 bucket ?&lt;/strong&gt;&lt;br&gt;
Because this is a flexible and low maintenance solution, compared to hosting on a server. Once the setup is done, the website update requires only a folder upload to a bucket. Also is it a very low cost solution, depending on the number of visits, starting at a few cents par month in my case, for a personal website with very low traffic.&lt;/p&gt;
&lt;h1&gt;
  
  
  Step by step guide
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;The requirements for this guide : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the source code for a static website&lt;/li&gt;
&lt;li&gt;an AWS account&lt;/li&gt;
&lt;li&gt;a DNS hosted in Route 53 (or the provider of your choise), domain name as &lt;code&gt;your.website.url&lt;/code&gt; in this tutorial&lt;/li&gt;
&lt;li&gt;bucket name as &lt;code&gt;my-bucket-name&lt;/code&gt; in this tutorial&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  1. Preparing your website source code
&lt;/h2&gt;

&lt;p&gt;First of all, we will need the sources for our static website, built by the framework of your choise, or in plain HTML/JS/CSS if you prefer. &lt;/p&gt;

&lt;p&gt;In my case, I'm developing my website with Astro framework with static site feature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command line installs all required Nodejs dependencies, and builds the final source code for the static website. &lt;br&gt;
By default the sources are generated in the folder &lt;code&gt;dist/&lt;/code&gt;. This content will be uploaded to our S3 bucket in order to host as a website. &lt;/p&gt;

&lt;p&gt;Is is important to have the following pages in the root of your website.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/index.html&lt;/code&gt;, the homepage for your website&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/error.html&lt;/code&gt;, the default error page, in the following example I also have a &lt;code&gt;404.html&lt;/code&gt; page, for 404 not found error redirections. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Configuring a Static Website on Amazon S3
&lt;/h2&gt;

&lt;p&gt;Once the source code for the website is prepared and the final sources are built, the next step is creating a bucket on Amazon S3 and uploading the prepared source files. This process involves setting up the S3 bucket, enabling the static website hosting, adjusting permissions, and setting up error redirection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the S3 Bucket
&lt;/h3&gt;

&lt;p&gt;Navigate to Amazon S3's Bucket Management and create a new bucket : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bucket name : my-bucket-name&lt;/li&gt;
&lt;li&gt;AWS region of your choise&lt;/li&gt;
&lt;li&gt;Uncheck option "Block all public access", this sets the bucket as public. &lt;/li&gt;
&lt;li&gt;Make sure to acknowledge the security settings and keep the default configurations.&lt;/li&gt;
&lt;li&gt;Once the bucket is created, upload the source files from step 1&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Check out &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html" rel="noopener noreferrer"&gt;AWS S3 bucket naming best practices&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Enabling Static Website Hosting
&lt;/h3&gt;

&lt;p&gt;Next, enable the static website hosting in the bucket properties. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to your bucket, then under tab Properties&lt;/li&gt;
&lt;li&gt;Edit the Static website hosting and enable &lt;/li&gt;
&lt;li&gt;Hosting type : "Host a static website"&lt;/li&gt;
&lt;li&gt;Index document : index.html (or the name of your homepage in the source files)&lt;/li&gt;
&lt;li&gt;Error document : error.html (or your specific error page in the source files)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save the changes made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You will find the static website URL for your bucket under the Properties tab, section Static website hosting&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Find more usefull information in the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/EnableWebsiteHosting.html" rel="noopener noreferrer"&gt;AWS documentation S3 : Enabling website hosting&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Adjusting Permissions
&lt;/h3&gt;

&lt;p&gt;At this stage, when you navigate to the website URL, you may encounter an error &lt;code&gt;403 Forbidden with code : AccessDenied&lt;/code&gt;. &lt;br&gt;
We will resolve this issue by adjusting the permissions in the bucket edition.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to your bucket, then under Permissions tab&lt;/li&gt;
&lt;li&gt;Scroll to Bucket Policy and edit &lt;/li&gt;
&lt;li&gt;Paste the following policy and set up your bucket as the resource (&lt;em&gt;replace &lt;code&gt;my-bucket-name&lt;/code&gt; with your bucket name&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Finally save the changes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PublicReadGetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket-name/*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Refer to the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html" rel="noopener noreferrer"&gt;AWS documentation S3 : Setting permissions for website access&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setting Up Error Redirection (optional)
&lt;/h3&gt;

&lt;p&gt;To provide a better user experience, you can set up automatic redirection to a custom page for non-existing pages (404 error). This process involves setting up redirection rules in the static website hosting configuration of your bucket.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to your bucket then under tab Properties&lt;/li&gt;
&lt;li&gt;Edit the Static website hosting configuration&lt;/li&gt;
&lt;li&gt;Paste the following redirection rule, adapt according to your setup (&lt;em&gt;replace &lt;code&gt;my-bucket-name.s3-website.eu-west-3.amazonaws.com&lt;/code&gt; with your S3 bucket website host&lt;/em&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"HttpErrorCodeReturnedEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"404"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Redirect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"HostName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-bucket-name.s3-website.eu-west-3.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"ReplaceKeyWith"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"404.html"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This step is optional. Checkout the possible redirections rules in the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-page-redirect.html?icmpid=docs_amazons3_console" rel="noopener noreferrer"&gt;AWS documentation S3: Configuring a webpage redirect&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Configuring SSL Certificate and CDN
&lt;/h2&gt;

&lt;p&gt;After setting up the static website on Amazon S3, the next step is securing your domain with an SSL certificate and setting up a Content Delivery Network (CDN) using Amazon CloudFront.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requesting an SSL Certificate
&lt;/h3&gt;

&lt;p&gt;Navigate to the Certificate Manager and request a new certificate for your domain. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remember that the certificate must be in the us-east-1 region, this is a Cloudfront requirement for now (november 2023) &lt;/li&gt;
&lt;li&gt;Request a public certificate&lt;/li&gt;
&lt;li&gt;Domain name : &lt;code&gt;your.website.url&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Choose the validation method that better suits you (DNS or email)&lt;/li&gt;
&lt;li&gt;Once the certificate is requested, the status is in "Pending validation"&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Follow the instructions for the validation process according to the chosen method, by email or DNS&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Validating certificate through Route 53
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If your DNS is not hosted in AWS Route 53, refer to the documentation of your DNS provider. You will need the CNAME name and value in the SSL certificate page&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Go to Route 53 under your DNS&lt;/li&gt;
&lt;li&gt;create a new record type CNAME&lt;/li&gt;
&lt;li&gt;copy the name and value from SSL certificate in the previous step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The validation process may take a few minutes to complete. &lt;/p&gt;

&lt;h2&gt;
  
  
  4. Setting up CDN and DNS for your website
&lt;/h2&gt;

&lt;p&gt;Now that we have our website running in our S3 bucket and that we have a SSL certificate for our custom DNS, we can complete the Cloudfront CDN setup and finally configure the website domain name. The domain name will redirect to the Cloudfront distribution. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create your Cloudfront distribution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to CloudFront and create a new distribution&lt;/li&gt;
&lt;li&gt;In the origin domain, paste your S3 bucket website host : &lt;code&gt;my-bucket-name.s3-website.eu-west-3.amazonaws.com&lt;/code&gt; (&lt;em&gt;replace with your S3 bucket website host&lt;/em&gt;)
&lt;/li&gt;
&lt;li&gt;Choose request redirects from HTTP to HTTPS&lt;/li&gt;
&lt;li&gt;Choose the certificate that you've just validated&lt;/li&gt;
&lt;li&gt;Add an alternate domain, pointing to your DNS &lt;code&gt;your.website.url&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you do not see the certificate, it might be one of the following : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;try to refresh the page, or the refesh button next to the certificate selection&lt;/li&gt;
&lt;li&gt;is the SSL certificate valid ?&lt;/li&gt;
&lt;li&gt;is the SSL certificate in the correct region required by Cloudfront (us-east-1 as of november 2023)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setting up domain name to Cloudfront distribution
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If your DNS is not hosted in AWS Route 53, refer to the documentation of your DNS provider. You will redirect your custom domain name to the Cloudfront distribution domain name found under the Cloudfront distribution and General tab. The domain name looks like the following : &lt;code&gt;&amp;lt;dsitribution ID&amp;gt;.cloudfront.net&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Route 53
&lt;/h4&gt;

&lt;p&gt;The last step is setting up your domain to point to the Cloudfront distribution. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to in Route 53&lt;/li&gt;
&lt;li&gt;Create an new record&lt;/li&gt;
&lt;li&gt;Name is your custom domain &lt;code&gt;your.website.url&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select Alias, and choose &lt;code&gt;Alias to CloudFront distribution&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Choose your created distribution &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a few minutes, your website will be available through your custom domain with the proper SSL certificate.&lt;/p&gt;

&lt;h4&gt;
  
  
  Other DNS providers
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Go to your DNS provider&lt;/li&gt;
&lt;li&gt;Create an new A record&lt;/li&gt;
&lt;li&gt;Name is your custom domain &lt;code&gt;your.website.url&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Value is the CDN distribution host : &lt;code&gt;&amp;lt;dsitribution ID&amp;gt;.cloudfront.net&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In conclusion, hosting a static website on Amazon S3 is a practical, low cost and efficient method for maintaining an online presence. The process involves building the website's source code, configuring the static website on Amazon S3, securing the domain with an SSL certificate, and setting up a CDN with Amazon CloudFront.&lt;/p&gt;

&lt;p&gt;This guide covered each of these steps in detail, from setting up the S3 bucket and enabling static website hosting, to adjusting permissions and setting up error redirection. Additionally, it touched on the steps to secure your domain, request an SSL certificate, and set up a CDN. Lastly, it highlighted the importance of finalizing DNS setup to ensure your website is available with the appropriate SSL certificate.&lt;/p&gt;

&lt;p&gt;By following these guidelines you can create, host, and manage their static websites in a secure and reliable environment. This not only ensures a seamless user experience but also leverages the power and flexibility of Amazon's web services ecosystem.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>cdn</category>
      <category>website</category>
    </item>
    <item>
      <title>Astro Starter Template : Blog enhanced with I18n</title>
      <dc:creator>Rebeca Murillo</dc:creator>
      <pubDate>Sun, 12 Nov 2023 07:25:17 +0000</pubDate>
      <link>https://dev.to/rebecamurillo/astro-starter-template-blog-enhanced-with-i18n-1853</link>
      <guid>https://dev.to/rebecamurillo/astro-starter-template-blog-enhanced-with-i18n-1853</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Welcome to this hands-on tutorial where we'll delve into the Astro Blog Starter i18n template, featuring i18n capabilities with translations in multiple languages. Just to clarify, we're working with a template project here, meaning you can spawn your own repository right from it. Our template of choice is derived from Astro's official Starter Kit, thus carrying its robust DNA. &lt;br&gt;
Before we get started, I recommend a &lt;strong&gt;basic understanding of Node.js and Astro&lt;/strong&gt; to make the most of this journey. Let's dive in!&lt;/p&gt;
&lt;h2&gt;
  
  
  Create a new Github repository from template
&lt;/h2&gt;

&lt;p&gt;To start, let's create a new repository from the &lt;a href="https://github.com/rebecamurillo/astro-blog-i18n-starter" rel="noopener noreferrer"&gt;Astro Blog Starter i18n template&lt;/a&gt;. You new repository will have the core features of the &lt;a href="https://github.com/withastro/astro/tree/latest/examples/blog" rel="noopener noreferrer"&gt;Astro Starter Blog template&lt;/a&gt; with the added features of i18n with translations in multiple languages.&lt;/p&gt;
&lt;h2&gt;
  
  
  Define your languages
&lt;/h2&gt;

&lt;p&gt;To configure your languages, you will need to configure the available languages, and start a translations object with languages properties.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup available languages and default language in the file &lt;code&gt;src/i18n/utils.ts&lt;/code&gt;. This is important for the header configuration and canonical URL for SEO.
&lt;em&gt;File &lt;code&gt;src/i18n/utils.ts&lt;/code&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LANGUAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;English&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Français&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;es&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Español&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_LANG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Initiate your translations keys in &lt;code&gt;src/i18n/ui.ts&lt;/code&gt;
&lt;em&gt;File &lt;code&gt;src/i18n/ui.ts&lt;/code&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Astro Blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Astro Blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;es&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Astro Blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Create a new blog post in English, Spanish and French languages
&lt;/h2&gt;

&lt;p&gt;Let's now add a new blog post, with translations. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the &lt;code&gt;src/content/blog&lt;/code&gt; folder, create a new folder and call it "new-blog", this will be the slug for the blog pages. &lt;/li&gt;
&lt;li&gt;Then, inside folder &lt;code&gt;src/content/blog/new-blog&lt;/code&gt;, create a new file for each language, in my case English &lt;code&gt;en.md&lt;/code&gt;, Spanish &lt;code&gt;es.md&lt;/code&gt;, and French &lt;code&gt;fr.md&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;In each file copy the data required in the collection.  Refer to the &lt;code&gt;src/content/config&lt;/code&gt; file for collection setup, if you would like to add any more data usefull to your blog posts. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Example of file &lt;code&gt;src/content/blog/new-blog/es.md&lt;/code&gt; content.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mi&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;primer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;blog'&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Lorem&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ipsum&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dolor&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;amet'&lt;/span&gt;
&lt;span class="na"&gt;pubDate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Jul&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;08&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2022'&lt;/span&gt;
&lt;span class="na"&gt;heroImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/blog-placeholder-3.jpg'&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Me'&lt;/span&gt;
&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;es'&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="gh"&gt;# Mi primer Blog ! &lt;/span&gt;
Lorem ipsum dolor sit amet, consectetur adipiscing elit...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding the content for each language, we can navigate to the blog section on the site to see our new blog post in each language. If the translations are appearing correctly, we have successfully added a new blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a new static page in English, Spanish and French languages
&lt;/h2&gt;

&lt;p&gt;Now, let's add a new static page with translations. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;code&gt;src/pages/[lang]&lt;/code&gt; create a new file new-page.astro. &lt;/li&gt;
&lt;li&gt;In &lt;code&gt;src/i18n/ui.ts&lt;/code&gt; add a translation key for each language in the corresponding property for en, es, fr.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;File &lt;code&gt;src/i18n/ui.ts&lt;/code&gt;&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Astro Blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newpage.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New page title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newpage.description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New page description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Astro Blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newpage.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nouvelle page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newpage.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nouvelle page description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;es&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Astro Blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newpage.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nueva pagina&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newpage.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nueva pagina description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Copy the Layout configuration from the index.astro for example. And set your translations keys using the function &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;File &lt;code&gt;src/pages/[lang]/new-page.astro&lt;/code&gt;&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../layouts/Layout.astro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getLangFromUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useTranslations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../i18n/utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLangFromUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticPaths&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newpage.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newpage.description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newpage.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Layout&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Check your new page in your browser &lt;a href="http://localhost:4321/en/new-page" rel="noopener noreferrer"&gt;http://localhost:4321/en/new-page&lt;/a&gt;, &lt;a href="http://localhost:4321/fr/new-page" rel="noopener noreferrer"&gt;http://localhost:4321/fr/new-page&lt;/a&gt;,  and &lt;a href="http://localhost:4321/es/new-page" rel="noopener noreferrer"&gt;http://localhost:4321/es/new-page&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's it! You have now added a new blog post and a new static page to your Astro blog project in multiple languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimization for SEO
&lt;/h2&gt;

&lt;p&gt;This Astro Starter Blog I18n Template is pre-optimized for SEO. This is a critical aspect of any online project as it enhances visibility and discoverability on search engines.&lt;/p&gt;

&lt;p&gt;One of the ways this optimization is achieved is through the use of canonical URLs. In the template, the default language is set to English. This means all canonical URLs will default to the English language versions. &lt;/p&gt;

&lt;p&gt;On the other hand, alternate URLs are defined accordingly to each language. For instance, in the Spanish version, the alternate languages (which are English and French in this case) will appear in the head block. This setup ensures that search engines accurately index all versions of the site, preventing duplication and enhancing SEO performance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d2unzii50pm6zlhsdda.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d2unzii50pm6zlhsdda.png" alt="SEO canonical and alternate URLs" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, the Astro sitemaps plugin, already installed in this project, automatically generates sitemaps. These sitemaps include all versions of the website in all languages, providing further assistance to search engines for indexing the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Astro Starter Blog I18n template comes with the base features for a static website, blog pages in Markdown, translations in multiple languages with i18n feature, SEO optimization, ensuring that your content is ready to reach an international audience right from the get-go.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
