Skip to main content

Building and Deploying a Vue.js Portfolio on GitHub Pages

NR Tech Studio Team
NR Tech Studio Team NR Tech Studio
15 min read

A Vue.js portfolio hosted on GitHub Pages is a single-page application built with Vue 3 and Vite, version-controlled in a Git repository, and deployed automatically through GitHub Actions to static hosting. It gives engineers and engineering managers a fast, zero-maintenance, and low-cost showcase for technical projects.

A common misconception across engineering teams is that building a personal portfolio with Vue.js requires complex cloud infrastructure, server-side containers, and expensive hosting subscriptions. In reality, a modern static frontend decoupled from heavy compute allows developers to showcase real production competencies while maintaining a total cost of ownership of zero dollars.

This architectural breakdown walks through the scaffolding, pipeline design, performance tuning, and budget dynamics of shipping a high-velocity developer portfolio using Vue 3, Vite, and GitHub Actions, bridging the gap between portfolio showcase code and production-grade engineering practices.

Why Deploy a Vue.js Portfolio on GitHub

A Vue.js portfolio deployed via GitHub provides a direct window into an engineer’s practical technical judgment, code hygiene, and deployment disciplines. When evaluating engineering candidates or technical leads, technical leaders look beyond resume buzzwords to assess whether an engineer understands fundamental release automation, asset pipeline optimization, and code modularity.

Using Vue 3 alongside GitHub provides several tactical advantages over commercial site builders and generic content management systems:

  • Zero Infrastructure Maintenance: GitHub Pages acts as a specialized Content Delivery Network (CDN) edge endpoint. You eliminate virtual machine patching, Docker runtime debugging, and operating system updates.
  • Automated Continuous Delivery: Every push to your main branch can trigger automated linting, unit testing, and static generation through GitHub Actions.
  • Direct Code Transparency: Prospective clients and hiring committees can inspect your component hierarchy, TypeScript typings, state management decisions, and git commit hygiene directly.
  • Ecosystem Maturity: Vue 3 with Vite provides sub-second hot module replacement (HMR), tree-shaking, and tiny JavaScript bundle footprints compared to bulkier frontend stacks.

Adopting this workflow demonstrates an understanding of the modern decoupled web. Instead of maintaining monolithic web servers, you leverage static site generation mechanics that minimize operational overhead.

Architectural Foundation: Scaffolding Vue 3 with Vite

To build a high-performance portfolio, start with the official Vite-powered scaffolding tool. Vite replaces legacy Webpack setups by compiling via native ES modules during development and bundling through Rollup for production builds.

Scaffold your new project by running the following command in your terminal:

npm create vue@latest

Select TypeScript, Vue Router for multi-view navigation, Pinia for lightweight state management, and ESLint plus Prettier for code consistency. Once configured, navigate to your project directory, install dependencies, and configure the project base URL inside vite.config.ts.

The base path configuration is the single most common failure point when deploying Vue single-page applications to GitHub Pages. Because project pages are hosted under a sub-path such as https://username.github.io/repository-name/, Vite must be explicitly configured to rewrite asset links relative to that sub-path:

import { fileURLToPath, URL } from 'node:url'\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\nexport default defineConfig({\n // Set the base path to match your GitHub repository name\n // For root user pages (username.github.io), keep base as '/'\n base: process.env.NODE_ENV === 'production'? '/portfolio-repo/': '/',\n plugins: [vue()],\n resolve: {\n alias: {\n '@': fileURLToPath(new URL('./src', import.meta.url))\n }\n }\n})

This configuration ensures that generated script tags, stylesheets, and static chunks load without returning HTTP 404 errors once deployed to the remote CDN edge.

Configuring Vue Router for Static GitHub Hosting

Routing in single-page applications requires deliberate alignment with your hosting infrastructure. Understanding the difference between HTML5 History mode and Hash mode is critical when designing a modern SPA architectural footprint, as static hosting environments do not possess dynamic fallback rewrites by default.

GitHub Pages operates as an object store backed by a web server. When a browser requests /projects directly, GitHub searches for an actual file named projects or projects/index.html. If that file is absent, it serves a standard 404 page rather than routing the request back to Vue’s root index.html.

You can resolve this architectural limitation through two primary strategies:

  1. Hash History (Simple): Uses an internal URL fragment (e.g. /#/projects). The browser never sends the segment after the hash mark to the hosting server, guaranteeing client-side route evaluation without 404 errors.
  2. HTML5 History with 404 Hack (Clean URLs): Keeps standard URLs (e.g. /projects) by copying index.html to a custom 404.html file during the build process, coupled with an inline redirection script.

Below is a production-ready router setup implementing Web Hash History for guaranteed uptime on GitHub Pages:

import { createRouter, createWebHashHistory } from 'vue-router'\n\nconst routes = [\n {\n path: '/',\n name: 'Home',\n component: () => import('@/views/HomeView.vue')\n },\n {\n path: '/projects',\n name: 'Projects',\n component: () => import('@/views/ProjectsView.vue')\n },\n {\n path: '/case-study/:id',\n name: 'CaseStudy',\n component: () => import('@/views/CaseStudyView.vue'),\n props: true\n }\n]\n\nexport const router = createRouter({\n // Hash history bypasses GitHub Pages 404 routing mismatches\n history: createWebHashHistory(),\n routes,\n scrollBehavior() {\n return { top: 0 }\n }\n})

Using hash-based routing guarantees that deep-linking across your resume or external portfolio references functions reliably without requiring complex rewrite scripts.

Automating Deployments with GitHub Actions CI/CD

Manual file uploads and pushing compiled distribution assets directly to git branches introduce configuration drift and technical debt. A resilient engineering workflow relies on an automated continuous integration and continuous delivery (CI/CD) pipeline that builds and verifies artifacts before publishing.

GitHub Actions provides native runners to compile your Vue project and publish the resulting bundle directly to GitHub Pages. To establish this pipeline, create a workflow configuration file at .github/workflows/deploy.yml.

name: Deploy Vue Portfolio\n\non:\n push:\n branches: [main]\n workflow_dispatch:\n\npermissions:\n contents: read\n pages: write\n id-token: write\n\nconcurrency:\n group: 'pages'\n cancel-in-progress: true\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - name: Checkout Source\n uses: actions/checkout@v4\n\n - name: Setup Node.js\n uses: actions/setup-node@v4\n with:\n node-version: 20\n cache: 'npm'\n\n - name: Install Dependencies\n run: npm ci\n\n - name: Run Linter & Tests\n run: |\n npm run lint\n npm run test:unit --if-present\n\n - name: Build Production Assets\n run: npm run build\n\n - name: Setup Pages Artifacts\n uses: actions/upload-pages-artifact@v3\n with:\n path: 'dist'\n\n deploy:\n environment:\n name: github-pages\n url: ${{ steps.deployment.outputs.page_url }}\n runs-on: ubuntu-latest\n needs: build\n steps:\n - name: Deploy to GitHub Pages\n id: deployment\n uses: actions/deploy-pages@v4

This declarative pipeline eliminates human error. Every commit undergoes automated linting and dependency verification before production artifacts reach the global CDN layer.

Fetching Real-time Metrics from the GitHub REST API

A high-impact developer portfolio does not rely on static text descriptions alone. Displaying dynamic project statistics, such as active pull requests, star counts, and primary language distributions directly from GitHub repositories, demonstrates your ability to consume external APIs cleanly.

Vue 3’s Composition API allows you to encapsulate this data fetching inside a reusable composable with built-in error handling and reactive state.

import { ref, onMounted } from 'vue'\n\nexport interface RepositoryMetadata {\n id: number\n name: string\n description: string\n html_url: string\n stargazers_count: number\n language: string\n updated_at: string\n}\n\nexport function useGitHubRepos(username: string) {\n const repos = ref<RepositoryMetadata[]>([])\n const loading = ref<boolean>(true)\n const error = ref<string | null>(null)\n\n const fetchRepositories = async () => {\n try {\n loading.value = true\n error.value = null\n const response = await fetch(\n `https://api.github.com/users/${username}/repos?sort=updated&per_page=6`,\n {\n headers: {\n Accept: 'application/vnd.github.v3+json'\n }\n }\n )\n\n if (!response.ok) {\n throw new Error(`GitHub API error: status ${response.status}`)\n }\n\n const data: RepositoryMetadata[] = await response.json()\n repos.value = data\n } catch (err) {\n error.value = err instanceof Error? err.message: 'Unknown network failure'\n } finally {\n loading.value = false\n }\n }\n\n onMounted(() => {\n fetchRepositories()\n })\n\n return { repos, loading, error }\n}

Consuming this composable inside your Vue template allows your portfolio to remain permanently up-to-date without requiring manual rebuilds every time you release open-source updates.

Portfolio Architecture and Component Design

Organizing a portfolio application requires the same modularity, cohesion, and separation of concerns applied to large enterprise frontends. Avoid monolithic view files that combine markup, state manipulation, and styling into thousand-line scripts.

A maintainable portfolio architecture follows this directory structure:

  • src/components/common/: Atomic UI elements (Buttons, Badges, Modals).
  • src/components/portfolio/: Domain-specific modules (ProjectCard, SkillMatrix, ExperienceTimeline).
  • src/composables/: Business logic, external API integrations, and reactive event listeners.
  • src/data/: Static project definitions, career milestones, and technical case study content decoupled from the rendering layer.
  • src/types/: Centralized TypeScript interface definitions.

By segregating static data from presentation templates, you allow your content to evolve without modifying complex presentation components. This structural discipline mirrors scalable enterprise codebases.

Full Lifecycle Cost Analysis: Self-Hosted vs GitHub Pages

When choosing where to run frontend projects, technical leads must evaluate Total Cost of Ownership (TCO). While commercial platforms like Vercel, Netlify, or self-hosted virtual machines offer specialized deployment tooling, GitHub Pages offers enterprise-grade hosting economics at zero operational cost.

The table below breaks down the concrete financial models associated with running developer portfolio infrastructure over a 12-month lifecycle:

Hosting Infrastructure Monthly Compute Cost Annual Domain / SSL Cost CI/CD Pipeline Allowance Estimated Annual TCO
GitHub Pages (Free Tier) $0.00 $12.00 (Optional Custom Apex) 2,000 Free Actions Minutes/mo $12.00
Self-Hosted Cloud VPS (AWS/DO) $6.00 to $12.00 $12.00 (Domain) + Maintenance Time External CI/CD or Runner Compute $84.00 to $156.00
Managed Platform (Paid Pro) $20.00 to $25.00 Included or $12.00 Domain 3,000 Included Minutes $240.00 to $300.00
Full-Stack Container Architecture $15.00 to $40.00 $12.00 + Container Registry Fees Included Compute Billing $192.00 to $492.00

For independent engineering contractors or developers offering portfolio development services to clients, hourly billing and engagement models typically align across these market rate bands:

Engagement Model Rate / Fee Range Scope of Delivery Maintenance Overhead
Hourly Contract Engineering $65.00 to $150.00 / hour Custom Vue 3 architecture, custom styling, CI/CD pipeline setup Billed at standard hourly rate
Fixed-Fee Project Retainer $1,200.00 to $3,500.00 Turnkey portfolio build, headless CMS integration, full SEO setup Optional $150.00/mo retainer
Template Customization Tier $400.00 to $900.00 Scaffolding existing open-source template, repository setup Zero client maintenance required

Deploying static assets to GitHub Pages reduces your ongoing infrastructure costs to practically zero, eliminating recurring cloud hosting expenses while retaining full engineering control.

Performance Benchmarks and Core Web Vitals Optimization

A slow developer portfolio creates an immediate negative impression. If your portfolio fails Core Web Vitals metrics, technical evaluators will assume your production applications suffer from similar performance bottlenecks.

To achieve top-tier Lighthouse scores (95+ across Performance, Accessibility, and Best Practices), implement the following optimizations:

  • Route-Level Code Splitting: Ensure view components load lazily using dynamic imports: () => import('./views/ProjectsView.vue').
  • Asset Compression and Modern Formats: Convert all project preview imagery to WebP or AVIF formats. Modern image formats reduce visual asset payloads by 40% to 70% compared to standard PNG files.
  • CSS Purging: If using Tailwind CSS or UnoCSS, configure the engine to purge unused class definitions during the build cycle, ensuring stylesheet bundles remain under 15 KB.
  • Font Preloading: Host web fonts locally rather than relying on external runtime CDNs. Inject rel="preload" tags in your index.html to prevent Cumulative Layout Shift (CLS).

Adhering to these optimization practices ensures your portfolio achieves an initial Largest Contentful Paint (LCP) under 1.2 seconds, even on constrained mobile connections.

Security Implications and Client-Side Hardening

While static hosting platforms mitigate common backend risks like SQL injection and server-side request forgery, static frontend applications remain vulnerable to client-side threats if not properly configured.

Address the following security considerations before publishing your repository to production:

  1. API Secret Exposure: Never bundle private GitHub personal access tokens (PATs) or backend administrative credentials inside your client-side JavaScript. Any token embedded in Vite client code (e.g. using VITE_ prefix) can be extracted using browser inspection tools. Use public unauthenticated API endpoints, or proxy requests through a serverless cloud worker.
  2. Cross-Site Scripting (XSS) Sanitization: When rendering markdown-driven project descriptions or portfolio case studies, avoid unescaped v-html directives. Always sanitize compiled markup using libraries such as DOMPurify:
import DOMPurify from 'dompurify'\n\n// Sanitize dynamic markdown strings before rendering to the DOM\nexport function renderSafeHtml(rawHtml: string): string {\n return DOMPurify.sanitize(rawHtml, {\n ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li', 'code', 'pre'],\n ALLOWED_ATTR: ['href', 'target', 'rel']\n })\n}

Implementing safe sanitization prevents malicious script injection when handling dynamic content or parsing external RSS and blog feeds.

Integrating a Headless CMS or Markdown Engine

Hardcoding portfolio content directly into Vue component templates creates unnecessary technical debt. When you want to update project descriptions, skills, or career milestones, you should not need to modify rendering logic.

A decoupled content workflow isolates your presentation layer from your project data. You can implement this separation using local Markdown files with frontmatter metadata or a headless Content Management System (CMS).

For a static GitHub-hosted site, loading local Markdown files using Vite’s import.meta.glob provides an ideal zero-latency architecture:

export interface CaseStudy {\n slug: string\n title: string\n description: string\n stack: string[]\n content: string\n}\n\n// Load all markdown files at build time\nconst markdownFiles = import.meta.glob('/src/content/projects/*.md', {\n as: 'raw',\n eager: true\n})\n\nexport function getAllCaseStudies(): CaseStudy[] {\n return Object.entries(markdownFiles).map(([path, rawContent]) => {\n const slug = path.split('/').pop()?replace('.md', '') || ''\n // Parse markdown and metadata headers\n return {\n slug,\n title: slug.replace(/-/g, ' ').toUpperCase(),\n description: 'Production architecture overview',\n stack: ['Vue 3', 'TypeScript', 'Vite'],\n content: rawContent\n }\n })\n}

This structure delivers the administrative convenience of a content management system without requiring an external backend database or recurring third-party API costs.

Monitoring, Observability, and Uptime Tracking

Production software requires continuous visibility into user experience, browser compatibility errors, and uptime. A personal engineering portfolio benefits from that same observability discipline.

To monitor your deployed frontend without compromising performance or privacy:

  • Real-User Monitoring (RUM): Integrate lightweight, privacy-focused analytics such as Cloudflare Web Analytics, Plausible, or Umami. These platforms collect page view metrics and Core Web Vitals without using intrusive tracking cookies.
  • Client-Side Exception Capture: Integrate an exception monitoring tool (such as Sentry or Bugsnag) into your Vue app initialization logic to capture uncaught runtime exceptions across different browser engines.
  • Automated Uptime Verification: Configure an automated GitHub Action or external synthetic monitor (such as UptimeRobot or Better Stack) to ping your portfolio daily. This ensures your DNS settings, SSL certificates, and hosting endpoints remain healthy.

Adding observability demonstrates that you approach web development with a production mindset, treating your personal portfolio as a reliable production system rather than a throwaway side project.

Mastering Full-Stack Architectures and Framework Integration

While a decoupled Vue 3 frontend hosted on GitHub Pages is an ideal solution for static portfolios and client showcases, modern web platforms often demand dedicated backend systems for dynamic business logic, database transactions, and microservice orchestration.

When your architectural requirements expand beyond static delivery toward transactional backends, connecting your Vue client to a structured server framework provides the necessary foundation for relational persistence, queuing, and secure authentication.

To explore how modern full-stack web applications bridge dynamic API backends with frontend frameworks, review our architectural setup guide on installing and configuring Laravel development environments. You will see how enterprise teams structure robust full-stack applications with automated testing, containerized runtimes, and continuous delivery pipelines.

[Explore our complete Laravel, Basics directory for more guides.](/topics/topics-laravel-basics/)

Factors That Affect Development Cost

  • Custom domain registration and DNS renewal
  • CI/CD build minute consumption over free allowances
  • Headless CMS subscription tiers if decoupled
  • Third-party monitoring and error tracking tools

A static portfolio deployed via GitHub Pages costs zero dollars per month for infrastructure, requiring only an optional annual custom domain purchase.

Frequently Asked Questions

Why does Vue Router return a 404 error when refreshing on GitHub Pages?

GitHub Pages hosts static files and lacks default server-side rewrite rules to point all requests to index.html. When you refresh a path like /projects using HTML5 history mode, the server searches for a physical file at that path and fails. Switching to Web Hash History or deploying a custom 404.html redirection script resolves this issue.

Can I use a custom domain with a Vue.js portfolio hosted on GitHub Pages?

Yes. Add a CNAME file containing your custom domain to your repository public directory. Then, configure your domain registrar DNS records with ALIAS, ANAME, or A records pointing to GitHub Pages IP addresses. GitHub automatically provisions and renews a free Let’s Encrypt SSL certificate.

How do I protect private API keys in a Vue.js portfolio deployed on GitHub?

You cannot hide private API secrets in client-side code, as any VITE_ prefixed environment variables are bundled directly into the compiled JavaScript. To use authenticated services securely, route your requests through an external proxy, such as a serverless Cloudflare Worker, that manages your secret tokens securely.

Is Vue 3 better than React for building a developer portfolio?

Vue 3 coupled with Vite delivers significantly faster local build times and smaller baseline bundle footprints than traditional React setups. Its single-file component architecture cleanly separates template, logic, and scoped styling, making your portfolio easier to maintain with lower architectural complexity.

Building and deploying a Vue.js portfolio on GitHub Pages bridges front-of-screen presentation with professional software delivery. By pairing Vue 3 and Vite with declarative GitHub Actions automation, you create a fast, resilient, and completely free web presence that showcases your code hygiene, system design capabilities, and attention to performance.

Evaluate your portfolio using this key decision checklist: configure your base path correctly inside vite.config.ts, implement predictable client routing via hash or rewritten fallback modes, isolate content from presentation logic, and enforce automated testing inside CI/CD pipelines. This engineering-first approach ensures your portfolio stands out to hiring managers and engineering clients alike.

References & Further Reading