My page.
- React 19 - UI library
- TypeScript - Type safety
- Vite - Build tool and dev server
- React Router - Client-side routing
- Tailwind CSS - Utility-first CSS framework
- shadcn/ui - Re-usable UI components
- pnpm - Package manager
- Playwright - End-to-end testing framework
- Independent project structure for framework-agnostic testing
- AWS S3 - Static website hosting
- AWS CloudFront - CDN for global content delivery
- Origin Access Control (OAC) - Secure access from CloudFront to S3
- Terraform - Infrastructure as Code
- AWS S3 - Remote state management with state locking
.
├── frontend/ # React application
│ ├── src/
│ │ ├── components/ # React components
│ │ │ └── ui/ # shadcn/ui components
│ │ ├── lib/ # Utility functions
│ │ ├── App.tsx # Main app with routes
│ │ ├── main.tsx # Application entry point
│ │ └── index.css # Global styles
│ ├── public/
│ └── package.json
├── e2e/ # E2E tests (independent project)
│ ├── tests/ # Test files
│ │ ├── home.spec.ts
│ │ ├── about.spec.ts
│ │ ├── contact.spec.ts
│ │ ├── projects.spec.ts
│ │ ├── theme-toggle.spec.ts
│ │ └── navigation.spec.ts
│ ├── playwright.config.ts
│ └── package.json
├── infrastructure/ # Terraform configuration
│ ├── main.tf
│ ├── cloudfront.tf
│ ├── variables.tf
│ └── outputs.tf
└── tasks/ # Taskfile task definitions
├── FrontendTasks.yml
├── InfrastructureTasks.yml
├── E2eTasks.yml
├── MiseTasks.yml
└── ...
- mise - for tool version management
- Task - for running tasks
- aws-vault - for AWS credential management (recommended)
- AWS CLI configured with appropriate credentials
- Run the setup task:
task setupThis will install all necessary tools defined in mise.toml.
Run task or task list to see all available tasks:
task listCommon tasks:
task check- Run all checks (Markdown, GitHub Actions, Frontend, Infrastructure)task setup- Setup project and install toolstask md:check- Check Markdown filestask md:fix- Auto-fix Markdown issuestask gh:check- Check GitHub Actions workflows
Frontend tasks:
task front:dev- Start development servertask front:build- Build for productiontask front:deps:add -- [package]- Add frontend dependenciestask front:deps:add:dev -- [package]- Add frontend dev dependenciestask front:check- Check frontend (lint + type-check)task front:lint- Run ESLinttask front:lint:fix- Auto-fix ESLint issuestask front:add-component -- [name]- Add shadcn/ui component
Infrastructure tasks:
task infra:init- Initialize Terraformtask infra:check- Check Terraform configuration (format + validate)task infra:plan- Show Terraform execution plantask infra:apply- Apply Terraform configuration
E2E testing tasks:
task e2e:install- Install E2E test dependencies and browserstask e2e:deps:add -- [package]- Add E2E dependenciestask e2e:deps:add:dev -- [package]- Add E2E dev dependenciestask e2e:test- Run E2E teststask e2e:test:ui- Run E2E tests in UI modetask e2e:test:debug- Run E2E tests in debug modetask e2e:test:headed- Run E2E tests with browser visibletask e2e:test:prod- Run E2E tests against productiontask e2e:report- Show E2E test report
Start the development server:
task front:devOr using pnpm directly:
cd frontend
pnpm install
pnpm devThe application will be available at http://localhost:5173.
Using Taskfile:
task front:dev- Start development servertask front:build- Build for productiontask front:preview- Preview production buildtask front:check- Run lint and type-checktask front:lint- Run ESLinttask front:lint:fix- Auto-fix ESLint issuestask front:type-check- Run TypeScript type checkingtask front:clean- Clean build artifacts
Using pnpm directly:
pnpm dev- Start development serverpnpm build- Build for productionpnpm preview- Preview production buildpnpm lint- Run ESLint
The project uses @/ as a path alias for the src/ directory:
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'Add shadcn/ui components:
task front:add-component -- buttonOr using pnpm:
cd frontend
pnpm dlx shadcn@latest add buttonExamples:
pnpm dlx shadcn@latest add card
pnpm dlx shadcn@latest add dialogThe E2E tests are in a separate, independent project to ensure they remain framework-agnostic and won't break when frontend dependencies are updated.
Install E2E test dependencies and browsers:
task e2e:installPrerequisites: Start the frontend development server before running tests:
task front:devThen in another terminal, run tests:
# Run all tests
task e2e:test
# Run tests in UI mode (interactive)
task e2e:test:ui
# Run tests in debug mode
task e2e:test:debug
# Run tests with browser visible
task e2e:test:headed
# Show test report
task e2e:reportTest against the production website:
task e2e:test:prodThis sets BASE_URL=https://23prime.xyz and runs tests against the live site.
home.spec.ts- Home page navigation and basic functionalityabout.spec.ts- About page displayprojects.spec.ts- Projects page displaycontact.spec.ts- Contact page and external linkstheme-toggle.spec.ts- Theme switching (light/dark/system)navigation.spec.ts- SPA routing and browser navigation
Configure AWS credentials using aws-vault:
aws-vault add defaultThe GitHub OIDC Provider must exist in your AWS account before applying infrastructure. This provider is shared across all GitHub Actions workflows in the account and only needs to be created once.
Check if it exists:
aws-vault exec default -- aws iam list-open-id-connect-providers | grep token.actions.githubusercontent.comIf it doesn't exist, create it:
aws-vault exec default -- aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1The Terraform state is stored in an S3 bucket. Ensure the bucket exists before running terraform init:
- Bucket:
tfstate-23prime-xyz-678084882233 - Region:
ap-northeast-1 - Encryption: Enabled
Copy the example file and configure your values:
cp infrastructure/terraform.tfvars.example infrastructure/terraform.tfvarsEdit infrastructure/terraform.tfvars:
# AWS Configuration
aws_account_id = "123456789012"
aws_region = "ap-northeast-1"
# Project Configuration
project_name = "your-project-name"
domain = "example.com"
# GitHub Configuration for OIDC Authentication
github_org = "your-github-org"
github_repo = "your-repo-name"Using Taskfile:
task infra:init
task infra:check
task infra:plan
task infra:applyUsing Terraform directly:
cd infrastructure
terraform init
terraform fmt
terraform validate
terraform plan
terraform applytask infra:init- Initialize Terraformtask infra:check- Check configuration (format + validate)task infra:fmt- Format Terraform filestask infra:validate- Validate configurationtask infra:plan- Show execution plantask infra:apply- Apply configurationtask infra:output- Show outputstask infra:show- Show statetask infra:destroy- Destroy infrastructuretask infra:clean- Clean Terraform files
After deployment, get outputs for GitHub Actions configuration:
task infra:outputOutputs include:
github_actions_role_arn- IAM role ARN for GitHub Actionss3_bucket_name- S3 bucket namecloudfront_distribution_id- CloudFront distribution IDcloudfront_domain_name- CloudFront domainwebsite_url- Website URL
Push to the main branch to trigger automatic deployment via GitHub Actions:
- Build frontend
- Deploy to S3
- Invalidate CloudFront cache
See .github/workflows/deploy.yml for details.
If you need to deploy manually:
# Build the frontend
task front:build
# Get infrastructure outputs
cd infrastructure
BUCKET_NAME=$(terraform output -raw s3_bucket_name)
DIST_ID=$(terraform output -raw cloudfront_distribution_id)
# Deploy to S3
aws s3 sync ../frontend/dist/ "s3://$BUCKET_NAME/" --delete
# Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id "$DIST_ID" \
--paths "/*"Configure the following variables (not secrets) in your GitHub repository (Settings → Secrets and variables → Actions → Variables):
AWS_ROLE_ARN- IAM role ARN fromtask infra:output(github_actions_role_arn)S3_BUCKET_NAME- S3 bucket name fromtask infra:output(s3_bucket_name)CLOUDFRONT_DISTRIBUTION_ID- CloudFront distribution ID fromtask infra:output(cloudfront_distribution_id)
The workflow uses AWS OIDC authentication, so no long-lived AWS credentials (secrets) are needed.
Tailwind CSS is configured with custom CSS variables for theming. The theme supports both light and dark modes.
CSS variables are defined in frontend/src/index.css and can be customized in tailwind.config.js.
MIT