Unfortunately S3 static doesn’t offer rewrite of urls, and doesn’t know that /page.html should resolve to /page and vice versa. This solution shows how to host a Nextjs static website on S3 without the html extension.

Configure S3 Bucket for static website hosting

  • Click to create a new bucket, choose your bucket name. Scroll to Block Public Access settings for this bucket and uncheck Block all public access. Check to Acknowledge the changes.
Hosting a static website using Amazon S3 Step 1
  • Click Create Bucket.
  • Click on your bucket, go to Properties.
Hosting a static website using Amazon S3 Step 2
  • Scroll to Static website hosting, and click on Edit.
Hosting a static website using Amazon S3 Step 2-2
  • Check to enable static website hosting, check Host a static website hosting type. Index document should be index.html, and error document leave as error.html. Click to save changes.
Hosting a static website using Amazon S3 Step 3
  • To be able to serve public files from your bucket, one more setting is needed. Go to Permissions tab of your bucket. Scroll to bucket policy and edit. Replace example code with your bucket name.
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "PublicRead",
			"Effect": "Allow",
			"Principal": "*",
			"Action": [
				"s3:GetObject",
				"s3:GetObjectVersion"
			],
			"Resource": "arn:aws:s3:::yourbucketname/*"
		}
	]
}
  • You’re ready to host your files publicly from your s3 bucket.

Programmatic access to S3 bucket

Your S3 bucket might need to be programmatically accessible from terminal or some automated process like Github actions.

And you don’t want to create IAM credentials with full s3 access.

To be able to permit only crud operations to your S3 bucket:

  • go to IAM settings, and add a new user.
iam policy access only your bucket step1
  • Go to Permissions tab, scroll down to set Permissions boundary. You can see policy examples on filter policies below. Choose create policy.
iam policy access only your bucket step2
  • Click on the json tab, and paste this (rename yourbuckethere with your bucket name).
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObjectAcl",
                "s3:GetObject",
                "s3:PutBucketAcl",
                "s3:ListBucket",
                "s3:DeleteObject",
                "s3:GetBucketAcl",
                "s3:GetBucketLocation",
                "s3:ListObjectsV2",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::yourbuckethere",
                "arn:aws:s3:::yourbuckethere/*"
            ]
        }
    ]
}
  • If you click on the visual tab, it should look something like the picture below.
IAM programmatic access policy
  • click to the next step, name your policy and click to create policy.
  • Go back to your user and click on attach existing policies directly, find your policy in filter and check.
iam attach policy
  • Click to create user, copy your Access key id and Secret access key, or download csv.

Static pages on s3 without .html extension

There’s an annoyance…/yourpage.html can only be accessed as /yourpage.html, and not /yourpage.

  • You could rewrite your urls to directories having an index.html
  • You could include another service on top of s3 to do the rewrites
  • Or you could rename html files and lose the html extension (but don’t forget to set the mimetype)

I don’t like any of the solutions, but third one seemed like most optimal one. First solution feels off, second maybe unnecessarily overengineering what should be simple.

I decided to do this task after exporting the nextjs build.

I stumbled upon this solution for jekyll files at https://kylewbanks.com/blog/jekyll-blog-on-s3-without-html-extension and modified the bash script for my needs:

#!/bin/bash

# Exit immediately if a command exits with a non zero status
set -e
# Treat unset variables as an error when substituting
set -u

function remove_html_extension() {
  TARGET=$1
  EXCLUDES_REGEX=$2

  for filename in $TARGET/*.html; do
    # File is html and not in $EXCLUDES_REGEX files
    if [[ $filename =~ ^.+\.html$ && ! $filename =~ $EXCLUDES_REGEX ]]; then
      original="$filename"
      # Get the filename without the path/extension
      filename=$(basename "$filename")
      extension="${filename##*.}"
      filename="${filename%.*}"

      # Move it
      mv $original $TARGET/$filename
    fi
  done
}

function upload_to_s3() {
  TARGET=$1
  BUCKET=$2
  DEST=$3
  # Now upload to s3, deleting any items that no longer exist
  aws s3 sync --delete $TARGET s3://$BUCKET/$DEST
  # Rename all files without the extension to mimetype text/html
  aws s3 cp \
    s3://$BUCKET/$DEST \
    s3://$BUCKET/$DEST \
    --exclude '*.*' \
    --no-guess-mime-type \
    --content-type="text/html" \
    --metadata-directive="REPLACE" \
    --recursive
}

function deploy() {
  TARGET=$1
  EXCLUDES_REGEX=$2
  BUCKET=$3
  DEST=$4

  remove_html_extension $TARGET $EXCLUDES_REGEX
  upload_to_s3 $TARGET $BUCKET $DEST
  rm -r $TARGET
}

deploy "[email protected]"

This should be run after next export command.

Automate with Github workflow

I wrote this down after I created a nextjs doc for my react components. You can see the deploy setup here. This solution is generic enough for other NextJS websites, so I wrote a Github action you can use https://github.com/marketplace/actions/deploy-static-website-to-s3. I think it should work for other static websites, but haven’t tested.

Liked it? Take a second to support Ana Bujan on Patreon!

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x