A lightning-fast, SEO-optimized portfolio website with automated AWS deployment
- Overview
- Features
- Getting Started
- Good to Know
- Key Technologies
- License
- Contributing
This portfolio template provides a production-ready website solution combining modern frontend technologies with automated cloud deployment. Built with React and TypeScript, it delivers exceptional performance while maintaining full SEO optimization and accessibility standards.
- Perfect 100/100 Lighthouse score for performance, accessibility, best practices, and SEO
- CDN-powered global content delivery for fast load times
- Optimal SEO configuration
- Fully statically rendered
- Modern glassmorphic design
- Responsive single-page application
- Easy content customization through a single file (content.tsx)
- Eye-catching animations, disabled if user prefers reduced motion
- Built with TypeScript & React
- Automated, low-cost, and secure AWS deployment pipeline (less than $5 per month)
- Integrated contact form system
- Terraform configuration for easy setup
- GitHub Actions for CI/CD when code changes
- CloudFormation for bootstrapping Terraform state storage
If you'd like to use this template to create your own portfolio website, click "use this template" at the top of the repository. You can then clone your new repository and follow the instructions below to build and deploy your site.
- Node.js >= 22
- pnpm >= 9.0
- Basic familiarity with React, TypeScript, and GitHub
- A code editor like VS Code or WebStorm
- (Optional) AWS account for cloud deployment
- (Optional) Domain name for live deployment
In order to build the website's static files, you'll need to follow these steps:
- Install Dependencies
# Navigate to the client directory cd client # Install project dependencies pnpm install
-
Update Site Content The site's content is managed through a single file (content.tsx) for simplicity:
# Edit the main content file vim src/content.tsx # or use your preferred editor
Why Static Content? This portfolio uses static content generation for optimal performance and SEO. While this means content updates require a rebuild, it eliminates the overhead and complexity of a CMS. For simple portfolio sites, this tradeoff improves page load times and reduces hosting costs.
If you need dynamic content, you can modify
content.tsx
to fetch data from a headless CMS or API. -
Replace Images
# Project thumbnails public/project/ # Add your project images here # Hero image public/hero.webp # Replace with your hero image # Favicon public/favicon/ # Generate from https://favicon.io and place here
Image Guidelines:
- Use WebP format with the lowest quality you can manage for optimal performance
- Project thumbnails: 500x180 recommended
- Hero image: Minimum 1920x1080px
- Favicon: Generate a complete set from favicon.io
-
Start Development Server
# Start local development pnpm dev # Access your site at http://localhost:5173
-
Build for Production
# Create optimized build pnpm build # Preview production build pnpm preview
The production build will be created in the
/dist
directory, ready for deployment.
-
Choose Your Deployment
Option A: Automated AWS Deployment
- Follow the AWS deployment guide in the next section
- Includes CDN, SSL certificate, DNS records, contact form submission API endpoint, and email notifications
Option B: Static Host
- Commit Your Changes
git add . git commit -m "customize portfolio content" git push origin main
To merge updates from the template repository:
# Add the template as a remote
git remote add template https://github.com/Unit2795/djoz-portfolio.git
# Fetch updates
git fetch template
# Merge updates (resolve conflicts if needed)
git merge template/main
You can also just clear your repository out, copy the files from the template repository to your own repository, and selectively add back your changes.
💡 Optional Deployment: You can alternatively host on platforms like Netlify or Vercel
⚠️ Warning: AWS services used in this deployment will incur costs (less than $5 per month). Make sure to monitor your usage to avoid unexpected charges.
This guide walks you through deploying your portfolio using AWS infrastructure. The deployment is automated using GitHub Actions and Terraform, providing you with a production-grade setup including CDN delivery and a serverless backend.
The deployment automatically provisions:
- CloudFront - Global CDN for fast content delivery
- S3 - Secure static file hosting & Terraform state storage
- DynamoDB - Terraform state locking
- Lambda - Serverless backend for contact form
- API Gateway - Endpoint for contact form submissions
- IAM - Identity and Access Management for secure AWS access
- CloudFormation - Bootstraps the Terraform state storage
- SES - Email handling for form submissions
- ACM - SSL/TLS certificate management
- Route 53 - DNS management (optional)
- AWS Account with administrative access
- GitHub account
- Registered domain name
- Basic AWS familiarity
- 30-45 minutes for setup
Create a Route 53 hosted zone for your domain using the AWS CLI or in the AWS Console
# Create Route 53 hosted zone (if using AWS DNS)
aws route53 create-hosted-zone \
--name yourdomain.com \
--caller-reference $(date +%s)
# Note the nameservers for your domain registrar
aws route53 get-hosted-zone --id /hostedzone/ZONEID
Using another DNS provider? See Alternative DNS Setup.
-
Create an IAM Identity Provider using AWS CLI or in the AWS console for GitHub Actions to use.
- Provider URL:
https://token.actions.githubusercontent.com
- Audience:
sts.amazonaws.com
- Provider URL:
-
Create an IAM Role
- Make note of the name you give this role, you will need it later.
- Select Web Identity as the trusted entity type.
- Set Identity Provider to the one you just created
token.actions.githubusercontent.com
- Set Audience to
sts.amazonaws.com
- Set the GitHub organization (your username if you don’t have one). You may also optionally specify the repo and
the branch.
- Example of multiple repos/branches:
"token.actions.githubusercontent.com:sub": [ "repo:<organization-or-username>/<repo-1>:ref:refs/heads/<branch-1>", "repo:<organization-or-username>/<repo-1>:ref:refs/heads/<branch-2>", "repo:<organization-or-username>/<repo-2>:ref:refs/heads/<branch-1>", "repo:<organization-or-username>/<repo-3>:ref:refs/heads/*" ]
- Copy your AWS account ID. It can be found in the top right of the AWS web Console when you click your name. The dropdown menu will show your “Account ID”
-
Add GitHub Secrets:
AWS_ACCOUNT_ID # Your AWS Account ID AWS_IAM_ROLE_NAME # Role name from step 2
-
Add GitHub Variables:
AWS_DEFAULT_REGION # e.g., us-east-1
-
Update Terraform variables:
# terraform/terraform.tfvars domain_name = "example.com" aws_region = "us-east-1" bucket_name = "djoz-portfolio" admin_email = "[email protected]"
-
(Optional) Configure state backend:
# terraform/state.config bucket = "tf-state-djoz-portfolio" key = "terraform.tfstate" dynamodb_table = "tf-state-djoz-portfolio-lock" region = "us-east-1"
-
(Optional) Select SES Email sending identity:
- By default, the template uses
Email-Based
sending identity for SES. If you have a verified domain, you can switch to theDomain-Based
sending identity by updating the ses.tf file.
- By default, the template uses
-
Push changes to trigger deployment:
git add . git commit -m "configure aws deployment and content" git push origin main
-
Monitor the deployment:
- Check GitHub Actions tab
- If you are using the Email-Based sending identity for SES: You'll receive an email from Amazon SES to verify your
admin_email
address provided in the terraform.tfvars file if you haven't already added this email to SES before. You must click the verification link before you can send emails from/to this address. This admin email is where you will receive contact form submissions. - Wait for CloudFront distribution (~15-30 min)
If not using Route 53:
- Remove Route 53 configurations from acm.tf and cloudfront.tf files.
- Configure DNS manually:
- Get ACM validation records from AWS Console
- Add CNAME records to your DNS provider
- Add CloudFront distribution CNAME
If you prefer not to use GitHub Actions, you can deploy the site locally using the AWS CLI and Terraform CLI. You'll need to install and configure the AWS CLI and Terraform CLI.
If you need to manually rebuild the site and force cache invalidation on CloudFront, you can set the
FORCE_INVALIDATION
environment variable to 1
in your GitHub repository. Then manually trigger the "Build and Deploy"
workflow. This will trigger a cache invalidation on CloudFront after the deployment.
If you want to take down the website, you can run the terraform-destroy
workflow in the GitHub Actions tab of your
repository. This will remove all the AWS resources that were created by Terraform. You may also manually run
terraform destroy
from your local machine if you have the AWS and Terraform CLIs installed and configured.
- The majority of the site's content is stored in the content.tsx file. Some content can be disabled entirely.
- All of the animations are disabled if the user prefers reduced motion.
- The site is fully responsive and optimized for mobile devices.
- The most complex part of the React code is the Navbar component, which handles the dynamically resizing navbar and the smooth scrolling to sections.
- All user-provided variables are stored in the terraform.tfvars and state.config files.
- The
/bootstrap
directory contains the CloudFormation code and shell script that creates the S3 bucket and DynamoDB table for Terraform state storage. - The DNS records for the
www.
subdomain are created automatically in addition to the root domain you provide. - The
admin_email
variable in the terraform.tfvars file is used for receiving contact form submissions. If this has not already been verified in SES, an email with a verification link will be sent. - If you create multiple instances of this portfolio, you will need to update the variable files to ensure the important variables are unique or the deploy will fail.
- The
terraform-destroy
workflow in the GitHub Actions tab will remove all the AWS resources that were created by Terraform. - The
terraform-apply
workflow in the GitHub Actions tab will apply the Terraform configuration to create the AWS resources, build the site, upload it, and invalidate the CloudFront cache.
- The index.js file contains the Lambda function code for the contact form submission.
- This function is triggered by an API Gateway POST request.
- The function sends an email to the
admin_email
address provided in the terraform.tfvars file.
- The auth.js file contains the Lambda function code for the API Gateway authorizer.
- This authorizer prevents too many requests from being made to the contact form submission endpoint.
- The default rate limit is 10 successful requests per month.
- The lambda function is triggered by an API Gateway request.
- The information about the number of requests that have been made is stored in a DynamoDB table.
- React
- Library for building the single-page application
- TypeScript
- Type safety for the React site
- PNPM
- Efficient package manager
- ESLint
- Linter for JavaScript and TypeScript
- Spacey
- ESLint shared config styleguide for TypeScript React projects
- Tailwind CSS
- Utility-first CSS styling for the React site
- Vite
- Build tool for the React site
- Terraform
- Deploys the site to AWS CloudFront and sets up a back-end for receiving contact form submissions
- GitHub Actions
- Automates the deployment of the site to AWS using Terraform
- CloudFormation
- Bootstraps the Terraform state backend in AWS
This project is licensed under the MIT License. You are free to use, modify, and distribute this code as you see fit. See the LICENSE file for more information.
If you have any suggestions, improvements, or issues, please open an issue or a pull request. I'd love to hear your feedback!