Skip to content

Repository files navigation

Portfolio — marlonkranz.com

Personal portfolio site built with Next.js 16. Bilingual (EN/DE), four switchable design themes with dark/light variants, self-hosted via Docker and Traefik.

Live Site · LinkedIn · Email

Features

  • Four design themes (Atelier, Iris, Manifesto, Cobalt), each with dark and light variants
  • Bilingual content (EN/DE) with path-based routing via next-intl
  • Per-theme hero animations built with Framer Motion
  • Project pages authored in MDX
  • Shareable URLs with theme preference via query params
  • Automated deployment: push to main -> Docker image built and deployed via GitHub Actions

Tech stack

Layer Technology
Framework Next.js 16 (App Router) + TypeScript
Styling Tailwind CSS v4
Animations Framer Motion
i18n next-intl (EN/DE, path-based)
Theming next-themes + custom CSS custom properties
Content MDX via next-mdx-remote
CI/CD GitHub Actions -> GHCR -> Docker + Traefik

Architecture

Theming has two independent layers: dark/light mode (managed by next-themes, stored as a class on <html>) and design themes (four palettes applied via data-theme, stored in localStorage). An inline script in <head> reads both before hydration to prevent flash.

All site content lives in content/{locale}/ — JSON files for structured data (experience, education, publications) and MDX for project detail pages. Content changes don't require touching application code.

Getting started

pnpm install
pnpm dev        # http://localhost:3000
pnpm build      # Production build
pnpm tsc --noEmit  # Type-check

Content

All content lives in content/{locale}/ and can be edited without touching application code:

  • content/{locale}/data/experience.json — Work experience
  • content/{locale}/data/education.json — Education
  • content/{locale}/data/certificates.json — Certificates
  • content/{locale}/data/about.json — Bio and intro text
  • content/{locale}/data/publications.json — Publications
  • content/{locale}/projects/*.mdx — Project detail pages

Where {locale} is en or de. English is the source of truth; German falls back to English where translations are missing.

Deployment

The site runs in Docker behind Traefik, deployed via GitHub Actions. Pushing to main builds a Docker image, pushes it to GHCR, and SSHes into the server to pull and restart the container.

Server setup

  1. Install Docker and Docker Compose on the server
  2. Add the deployment public key to ~/.ssh/authorized_keys
  3. Create a docker-compose.yml on the server:
portfolio:
  image: ghcr.io/conava/portfolio-website:latest
  restart: unless-stopped
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.portfolio.rule=Host(`marlonkranz.com`)"
    - "traefik.http.routers.portfolio.entrypoints=websecure"
    - "traefik.http.routers.portfolio.tls.certresolver=letsencrypt"
    - "traefik.http.services.portfolio.loadbalancer.server.port=3000"
  networks:
    - traefik

GitHub Actions secrets

Add these in Settings > Secrets and variables > Actions:

Secret Value
DEPLOY_HOST Server IP or hostname
DEPLOY_USER SSH user
DEPLOY_KEY Private SSH key (unencrypted, contents of ~/.ssh/deploy_key)
DEPLOY_PATH Absolute path to docker-compose.yml on the server

Updating content

Edit MDX/JSON files and push. The pipeline runs automatically.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages