Personal portfolio website built with Next.js 16, featuring a blog, project showcase, and AI-powered interactions.
Live: https://sachi-dev-809350176771.asia-southeast1.run.app
- Framework: Next.js 16 with React 19
- Runtime: Bun
- Styling: Tailwind CSS 4
- Content: Velite (MDX-based content management)
- UI Components: Radix UI, Framer Motion
- AI: Google Gemini API
- Deployment: Google Cloud Run
Create a .env.local file:
GEMINI_API_KEY=YOUR_GEMINI_API_KEY
UPSTASH_REDIS_REST_URL=YOUR_UPSTASH_REDIS_REST_URL
UPSTASH_REDIS_REST_TOKEN=YOUR_UPSTASH_REDIS_REST_TOKEN# Install dependencies
bun install
# Run development server
bun run devOpen http://localhost:3000 to view the site.
# Development mode with hot reload
bun run docker:dev
# Production build
bun run docker:prod├── content/
│ ├── blogs/ # MDX blog posts
│ ├── projects/ # MDX project descriptions
│ └── assets/ # Content images
├── src/
│ ├── app/ # Next.js app router pages
│ ├── components/ # React components
│ ├── config/ # Site configuration
│ ├── lib/ # Utility functions
│ ├── styles/ # Global styles
│ └── types/ # TypeScript types
├── public/ # Static assets
└── velite.config.ts # Velite content configuration
Deployed on Google Cloud Run (Asia Southeast 1 region).
- Install Google Cloud CLI
- Authenticate with your Google account:
gcloud auth login
- Set your project:
gcloud config set project YOUR_PROJECT_ID
On first deployment, GCP will prompt you to enable required APIs:
artifactregistry.googleapis.comcloudbuild.googleapis.comrun.googleapis.com
Grant IAM permissions to the default Compute Engine service account. Replace PROJECT_NUMBER with your project number (found in GCP Console):
# Grant Cloud Build permissions
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/cloudbuild.builds.builder"
# Grant Storage permissions
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/storage.objectAdmin"
# Grant Cloud Run permissions
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/run.admin"
# Grant Service Account User permissions
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/iam.serviceAccountUser"Tip: Find your project number by running
gcloud projects describe YOUR_PROJECT_ID --format="value(projectNumber)"
bun run deployThis command:
- Loads environment variables from
.env.local - Builds the Docker image using Cloud Build
- Deploys to Cloud Run with the configured environment variables
- Makes the service publicly accessible
source .env.local && gcloud run deploy sachi-dev \
--source . \
--region=asia-southeast1 \
--allow-unauthenticated \
--set-env-vars=GEMINI_API_KEY=$GEMINI_API_KEYPermission Denied Error:
If you see PERMISSION_DENIED: Build failed because the default service account is missing required IAM permissions, follow the "First-Time Setup" section above to grant the necessary roles.
Switch GCP Account:
# List authenticated accounts
gcloud auth list
# Switch to a different account
gcloud config set account [email protected]
# Or login with a new account
gcloud auth loginEnvironment Variable Validation Errors (e.g., "must be a valid URL"):
Do not use quotes around values in .env.local. The deploy script passes values literally to Cloud Run, so quoted values will have embedded quotes.
# Wrong - quotes become part of the value
UPSTASH_REDIS_REST_URL="https://example.upstash.io"
# Correct - no quotes
UPSTASH_REDIS_REST_URL=https://example.upstash.io| Command | Description |
|---|---|
bun run dev |
Start development server |
bun run build |
Build for production |
bun run start |
Start production server |
bun run lint |
Run ESLint |
bun run docker:dev |
Run dev server in Docker |
bun run docker:prod |
Build and run production in Docker |
bun run deploy |
Deploy to GCP Cloud Run |
MIT