Skip to main content

Overview

This portfolio is built with Astro, which can be deployed to virtually any hosting platform that supports static sites or Node.js. This guide covers the most popular deployment options.

Build Process

Local Build

Before deploying, test the production build locally:
npm run build
This command:
  1. Compiles TypeScript and Astro components
  2. Processes and optimizes CSS with Tailwind
  3. Minifies JavaScript using Terser
  4. Removes console logs and debugger statements
  5. Generates static HTML files
  6. Outputs to the dist/ directory

Build Configuration

The build is optimized in astro.config.mjs:
astro.config.mjs
export default defineConfig({
  integrations: [tailwind({ applyBaseStyles: false })],
  vite: {
    build: {
      cssMinify: 'esbuild',
      minify: 'terser',
      terserOptions: {
        compress: {
          drop_console: true,
          drop_debugger: true
        }
      }
    }
  }
});
Optimizations:
  • CSS minification with esbuild (fast)
  • JavaScript minification with Terser (smaller output)
  • Console logs removed in production
  • Debugger statements removed

Preview Production Build

Test the production build before deploying:
npm run preview
This starts a local server serving the built files from dist/.
The preview server runs at http://localhost:4321 by default. Check that all features work correctly, especially:
  • Language switching
  • Dark mode toggle
  • Internal navigation
  • Asset loading

Deploying to Vercel

Vercel is the recommended platform for this portfolio. It offers excellent performance, automatic HTTPS, and seamless deployments.

Prerequisites

  • GitHub, GitLab, or Bitbucket account
  • Your portfolio code pushed to a repository
  • Vercel account (free tier available)

Deployment Steps

1

Import project

  1. Go to vercel.com
  2. Click “Add New” → “Project”
  3. Import your Git repository
2

Configure build settings

Vercel auto-detects Astro projects. Verify these settings:
  • Framework Preset: Astro
  • Build Command: npm run build
  • Output Directory: dist
  • Install Command: npm install
3

Deploy

Click “Deploy” and wait for the build to complete. Your site will be live at your-project.vercel.app.
4

Add custom domain (optional)

In Project Settings → Domains, add your custom domain and follow the DNS configuration instructions.

Vercel Configuration

The project includes a vercel.json configuration file:
vercel.json
{
  "cleanUrls": true,
  "trailingSlash": false,
  "headers": [
    {
      "source": "/(.*)\\.(js|css|json|html|xml)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
}
Configuration explained:
Removes .html extensions from URLs:
  • /es/index.html/es/
  • Improves SEO and user experience
URLs won’t have trailing slashes:
  • /es
  • /es/
Aggressive caching for static assets:
  • max-age=31536000 - Cache for 1 year
  • immutable - File never changes (uses content hashing)
  • Applies to JS, CSS, JSON, HTML, XML files

Automatic Deployments

Vercel automatically deploys:
  • Production: Every push to main branch
  • Preview: Every pull request
Each deployment gets a unique URL for testing.
Use preview deployments to test changes before merging to production. Share preview URLs with others for feedback.

Deploying to Netlify

Netlify is another excellent option with a generous free tier.

Deployment Steps

1

Connect repository

  1. Go to netlify.com
  2. Click “Add new site” → “Import an existing project”
  3. Connect your Git provider and select repository
2

Configure build

Set these build settings:
  • Build command: npm run build
  • Publish directory: dist
  • Production branch: main
3

Deploy

Click “Deploy site”. Your site will be live at random-name.netlify.app.

Netlify Configuration

Create netlify.toml in your project root:
netlify.toml
[build]
  command = "npm run build"
  publish = "dist"

[build.environment]
  NODE_VERSION = "20"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-Content-Type-Options = "nosniff"
    Referrer-Policy = "strict-origin-when-cross-origin"

[[headers]]
  for = "/*.js"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/*.css"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

Deploying to Cloudflare Pages

Cloudflare Pages offers global CDN distribution and excellent performance.
1

Connect Git repository

  1. Go to Cloudflare dashboard → Pages
  2. Click “Create a project” → “Connect to Git”
  3. Authorize and select repository
2

Configure build

Set these settings:
  • Production branch: main
  • Build command: npm run build
  • Build output directory: dist
  • Root directory: /
3

Environment variables (if needed)

Add any required environment variables in the settings.
4

Deploy

Click “Save and Deploy”. Your site will be live on Cloudflare’s global network.

Cloudflare Configuration

Create _headers file in public/ directory:
public/_headers
/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin

/*.js
  Cache-Control: public, max-age=31536000, immutable

/*.css
  Cache-Control: public, max-age=31536000, immutable

Other Deployment Options

GitHub Pages

Add GitHub Actions workflow .github/workflows/deploy.yml:
.github/workflows/deploy.yml
name: Deploy to GitHub Pages

on:
  push:
    branches: [ main ]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./dist
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Custom Server (VPS/Docker)

For self-hosting on a VPS:
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Performance Optimization

Enable Compression

Most platforms enable gzip/brotli automatically, but verify:
  • Vercel: Automatic
  • Netlify: Automatic
  • Cloudflare: Automatic (with even better compression)
  • Custom server: Configure in nginx/Apache

CDN Configuration

Ensure static assets are served from CDN:
  1. Use the public/ directory for images, fonts, etc.
  2. Reference with absolute paths: /favicon.svg
  3. Enable long-term caching for hashed assets

Image Optimization

While this portfolio doesn’t have many images, for future additions:
---
import { Image } from 'astro:assets';
import myImage from '../assets/image.png';
---

<Image src={myImage} alt="Description" />

Environment Variables

If you need environment variables (for analytics, etc.):
Create .env file:
.env
PUBLIC_ANALYTICS_ID=your-id
Only environment variables prefixed with PUBLIC_ are exposed to the browser. Never expose secrets!

Monitoring

Performance Monitoring

Use these tools to monitor your deployed site:

Analytics

Consider adding privacy-friendly analytics:

Troubleshooting

  • Run npm install locally to ensure dependencies are correct
  • Check that all imports use correct paths
  • Verify package.json and package-lock.json are committed
  • Verify build output directory is set to dist
  • Check that routes follow Astro’s file-based routing
  • Ensure vercel.json or platform config is correct
  • Check that localStorage is accessible
  • Verify the theme toggle script runs before paint
  • Some platforms may have CSP restrictions
  • Verify i18n configuration in astro.config.mjs
  • Check that all language page files exist
  • Test URL structure matches expected pattern

Next Steps

Customization

Customize your deployed portfolio

Dark Mode

Fine-tune dark mode behavior

Build docs developers (and LLMs) love