Skip to content
serivaPublic

About

Minimalist SPA portfolio website with dynamic theming.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

About

Personal portfolio website built with GoFront using .templ component architecture. Content managed through YAML configuration and markdown files.

Key Features: GoFront templ components • Path-based SPA routing • Markdown blog & pages • Fuzzy search (Fuse.js) • Light/Dark themes • GitHub integration • Optional comments (giscus) & contact form (EmailJS)

Tech Stack

  • Core: GoFront (.templ components, Go-inspired frontend architecture)
  • Build: gofront prep & npm run prod • Biome (lint/format) • Playwright (10 E2E suites)
  • Content: YAML config (compiled to JSON) + Markdown • Marked.js • Prism.js v1.30 • Mermaid (lazy-loaded from jsDelivr for ```mermaid fences)
  • Features: Fuse.js (search) • EmailJS (contact form) • giscus (comments)
  • Assets: Raleway fonts • Inline SVG icons (local, no CDNs)

Architecture

The application is written in GoFront under app/src/ and compiles to native JavaScript ES modules.

Overview

flowchart LR
    subgraph Source ["Source (app/src/)"]
        SRC["Go & .templ Files"]
        DATA["YAML & Markdown"]
    end

    subgraph Build ["Build Pipeline"]
        GF["GoFront Compiler"]
        PREP["gofront prep"]
    end

    subgraph Runtime ["Browser Runtime"]
        APP["app.js (Router & UI)"]
        VENDOR["vendor.js (Libs)"]
        DOM["DOM (gom.Mount)"]
    end

    SRC --> GF --> APP --> DOM
    DATA -.-> APP
    PREP --> VENDOR --> DOM
Loading

Request Lifecycle

flowchart TD
    NAV["Navigation Event<br/>(Link click or popstate)"] --> ROUTE["Router<br/>Parse path & match route"]
    ROUTE --> RESOLVE{"Content Cached?"}
    RESOLVE -->|Yes| RENDER["Render View<br/>Mount .templ components to DOM"]
    RESOLVE -->|No| FETCH["Fetch & Parse Markdown<br/>(Marked.js & Prism)"] --> RENDER
Loading

Key Modules

  • Source (app/src/): main.go (init, global event delegation) • ui.go (region renders, overlay state reconciliation) • store.go (state & data store, JSON hydration, contentCache) • view.go (ViewState + pure route resolvers) • router.go (parseRoute, SPA routing, loadRoute fetch/cache/render path) • theme.go (theme persistence & CSS variables) • markdown.go (markdown & code highlighting) • search.go (Fuse.js search) • email.go (EmailJS integration) • icons.go (SVG icon registry) • comments.go (giscus comments)
  • Components (app/src/*.templ): app.templ • navbar.templ • blog.templ • projects.templ • page.templ • footer.templ • search.templ • contact.templ • icons.templ
  • Styles (app/css/app.css): single plain stylesheet linked from index.html, formatted and linted by Biome; loads in parallel with the JS bundles

Development

Prerequisites: Node.js >= 24.0.0, npm >= 11.0.0

Getting Started

npm install        # Install dependencies
npm run prepare    # Bundle fonts, themes, dependencies
npm run dev        # Start dev server (http://localhost:8181)

Open http://localhost:8181 in your browser.

Build for Production

To create an optimized production build:

npm run prod

This will:

  • Run code quality checks (biome check)
  • Let gofront build replace the previous public/ output so removed posts and routes never linger
  • Compile YAML configuration to content.json
  • Bundle and minify vendor dependencies (gofront prep --minify)
  • Compile, minify, and mangle GoFront application bundle to public/app.js
  • Let GoFront copy standard web assets to public/, then copy selective blog/page Markdown to public/data/; 404.html is emitted as a copy of the processed index.html
  • Generate sitemap.xml and rss.xml
  • Output complete, self-contained site to public/ directory

Asset Management

  • Vendor Assets (assetCopy): Fonts and Prism themes are copied from npm packages to app/ during npm run prepare via GoFront's assetCopy configuration in package.json. Note: app/fonts/ and app/css/prism-themes/ are gitignored as they are generated from npm packages.
  • Production Assets: gofront build synchronizes standard web files from app/ to public/. Runtime data is finalized separately: content.json plus only Markdown or files referenced by live routes are copied to public/data/ (never content.yaml).

Code Quality Tools

The project uses Biome for code formatting and linting (JavaScript in scripts/ and tests/build/, and the stylesheet app/css/app.css):

  • Format code: npm run format
  • Check code quality: npm run check
  • Auto-format: Enabled on save in VS Code

All code changes must pass linting before deployment.

Testing

The test suite covers unit tests and full end-to-end integration tests:

npm run test:gofront # Run Go unit tests (gofront test, jsdom-backed for DOM code)
npm run test:e2e     # Run E2E tests (Playwright, requires dev server)
npm run test:build   # Run the production build and verify public/ output (node:test; rebuilds public/)
npm run test:all     # Run all tests (GoFront + Playwright + build; runs last, rebuilds public/)

Tests cover:

  • Go Unit Tests (app/src/*_test.go): route parsing, ViewState resolvers (cache hit/miss, not found, repo-less projects), loadRoute (stubbed fetch, superseded navigation), content JSON hydration and date sorting, theme precedence/persistence and CSS variable application (jsdom), search guards and result mapping (fake Fuse index), frontmatter stripping, contact validation, render helpers
  • End-to-End Tests (tests/e2e/): 10 Playwright test suites across Chromium and Firefox:
    • Navigation, history, deep-linking, and route transitions
    • Markdown blog rendering, pagination, and code syntax highlighting
    • Project showcase, links, and tags
    • Light/Dark theme switching and localStorage persistence
    • Fuse.js search modal and query matching
    • EmailJS contact form validation and submission
    • Mobile hamburger navigation drawer
    • 404 and error state fallbacks
  • Type Checking: GoFront type checker (npx gofront check)

All quality gates and tests must pass before production builds.

GoFront Component Architecture

The frontend is built with GoFront using declarative .templ components and Go:

package main

templ BlogPostCard(post BlogPost) {
    <article class="blog-post-card">
        <h2 class="post-title">
            <a href={ "/blog/" + post.Slug } data-action="nav">{ post.Title }</a>
        </h2>
        <div class="post-meta">
            <time>{ post.Date }</time>
        </div>
        <p class="post-excerpt">{ post.Excerpt }</p>
    </article>
}

Architecture Highlights:

  • Zero Runtime Overhead: .templ files compile directly to native DOM manipulation calls (createElement, setAttribute, appendChild) with no virtual DOM diffing.
  • Go Syntax & Type Safety: Components receive typed props and compile to clean ES modules.
  • Dynamic Content Injection: Markdown generated from marked is injected using @templ.Raw(doc.HTML).
  • Mermaid Diagrams: ```mermaid fences in blog posts, pages and project READMEs are rendered as SVG. Mermaid is fetched from jsDelivr only when a rendered page contains a diagram, and diagrams are re-drawn with the matching dark/default theme when the theme toggles.
  • Global Event Delegation: Handled via data-action attributes registered centrally on #app in app/src/main.go.

Routing & SPA Support

Path-based URLs: /, /blog/, /blog/post-slug, /project/id, /page/id

Dev Server: npm run dev (gofront dev, port 8181) provides live reload and built-in SPA route fallback (serving index.html on clean paths)

GitHub Pages: Every known route gets a pre-generated index.html stub (200 with route-specific meta). 404.html is the app shell itself — GitHub Pages serves it at the requested URL without redirecting, so app/src/router.go reads location.pathname directly for unknown deep links. No hash redirect.

Absolute Paths: All resources use root-relative paths (/app.js, /data/content.json) to work from any route depth

Event Delegation: Dynamic content uses data-action attributes (e.g., <a data-action="nav">, <button data-action="toggle-theme">) handled centrally in app/src/main.go

Features & Configuration

All content is managed through app/data/content.yaml. The configuration file supports:

Projects

projects:
  - id: "my-project"
    title: "Cool Project"
    tags: ["JavaScript"]
    order: 1
    github_repo: "my-project"  # Auto-loads README
    demo_url: "https://example.com"
    youtube_videos: ["videoId"]
    links:
      - title: "GitHub"
        icon: "github"
        href: "https://github.com/user/repo"

Features: Auto-load GitHub READMEs • YouTube embeds • SVG icons • Tag organization

Blog

Create posts in app/data/blog/ with frontmatter (title, date, excerpt, tags), register in content.yaml:

blog:
  postsPerPage: 5
  posts:
    - filename: "2025-10-21-post-title.md"
      title: "Post Title"
      date: "2025-10-21"
      excerpt: "Summary"
      tags: ["tag1"]

Features: GFM markdown • Syntax highlighting • Pagination • Auto-sorting • Tags

Search

Fuse.js fuzzy search across projects and blog posts (conditionally loaded):

site:
  search:
    enabled: true
    minChars: 2

Features: Fuzzy matching • Weighted results (title 40%, desc 30%, tags 20%, content 10%) • Clickable tag filters • Real-time with debounce • Up to 8 results • Offline support

Pages

Create markdown files in app/data/pages/, configure in content.yaml:

pages:
  about:
    title: "About"
    showInNav: true
    order: 1

Theme (Light/Dark Mode)

Toggle button with localStorage persistence, system preference support:

site:
  theme:
    default: "dark"  # "dark", "light", "auto"
    dark:
      primary: "#10B981"
      code:
        theme: "prism-tomorrow"
    light:
      primary: "#047857"
      code:
        theme: "prism-coy"

Prism themes: prism-tomorrow, prism-okaidia, prism-dark, prism-coy, prism-solarizedlight (bundled locally)

Comments (giscus)

GitHub Discussions-powered comments via giscus:

site:
  comments:
    blogEnabled: true
    projectsEnabled: true
    repo: "username/repo"
    repoId: "R_YOUR_REPO_ID"
    categoryId: "DIC_YOUR_CATEGORY_ID"

Setup: Enable Discussions on repo → Install giscus app → Get IDs from giscus.app

Contact Form (EmailJS)

Modal form with email delivery (conditionally loaded):

site:
  emailjs:
    enabled: true
    serviceId: "service_xxx"
    templateId: "template_xxx"
    publicKey: "your_public_key"

Setup: Sign up at emailjs.com → Create service/template with variables {{title}}, {{name}}, {{email}}, {{time}}, {{message}}

Internationalization (i18n)

Built-in i18n framework (currently English only):

site:
  i18n:
    defaultLanguage: "en"
    availableLanguages: ["en"]

translations:
  en:
    "nav.projects": "Projects"
    "search.placeholder": "Search..."
    # ... more translations

To add a language: Add to availableLanguages (["en", "nl"]), copy en translations and translate values, use i18n.setLanguage('nl') to switch

About

Minimalist SPA portfolio website with dynamic theming.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages