Personal Portfolio Site: Vite + TypeScript + Markdoc, with a custom design system and client-side router. https://ehouse.io
  • TypeScript 51.1%
  • CSS 45.8%
  • JavaScript 1.8%
  • HTML 0.8%
  • Dockerfile 0.5%
Find a file
2026-04-04 00:07:35 -04:00
public Created a new site favicon 2026-03-19 10:46:12 -04:00
scripts New script to create new posts 2026-03-16 10:31:19 -04:00
src Snappier title and a few edits to the post 2026-04-04 00:07:35 -04:00
.dockerignore Updated with dockerfiler and new README.md 2026-03-17 02:11:18 -04:00
.gitignore Initial Commit 2026-03-14 02:16:16 -04:00
Dockerfile Added a $NGINX_PORT to Dockerfile and nginx.conf 2026-03-19 01:44:53 -04:00
index.html Created a new site favicon 2026-03-19 10:46:12 -04:00
nginx.conf Added a $NGINX_PORT to Dockerfile and nginx.conf 2026-03-19 01:44:53 -04:00
package-lock.json Added syntax highlighting to code segments 2026-03-26 00:04:46 -04:00
package.json Added syntax highlighting to code segments 2026-03-26 00:04:46 -04:00
README.md Removal of Tailwind V4 from the project 2026-03-17 12:18:33 -04:00
tsconfig.json Add first reactive project page with embedded React 2026-03-21 14:07:30 -04:00
vite.config.ts Snappier title and a few edits to the post 2026-04-04 00:07:35 -04:00

ehouse.io

Personal portfolio. Vite + TypeScript, with a custom Vite build framework and theme.

Stack

  • Vite - bundler / dev server
  • TypeScript - plain TS modules
  • Markdoc - blog post authoring
  • CSS - raw custom properties and component classes in src/style/theme.css

Dev

npm install
npm run dev

Posts

Create src/content/posts/<slug>.md with frontmatter:

---
title: Post Title
date: 2026-03-17
tag: Category
---

Body content.

Custom Markdoc tags

Tag Usage
{% callout %}...{% /callout %} Aside block
{% photo src="" alt="" caption="" %} Inline photo (caption optional)

Add tags in src/content/markdoc-config.ts.

Projects

Create src/pages/projects/<slug>.ts. Export meta and render():

import type { ProjectMeta } from "../projects";
import { Nav } from "../../components/nav";
import { Layout } from "../../components/layout";
import { ProjectHeader } from "../../components/project-header";
import { html } from "../../html";

export const meta: ProjectMeta = {
  slug: "your-slug", // must match filename
  title: "Project Title",
  tag: "TypeScript",
  description: "Short description for the card.",
  photo: "https://...", // cover image URL, or "" for none
};

export function render(): string {
  return Layout(html`
    ${Nav("projects")}
    <main class="section">
      <div class="post-panel">
        ${ProjectHeader(meta)}
        <p>Write-up goes here.</p>
        <a
          href="/projects"
          data-link
          class="btn btn-ghost"
          style="margin-top: 2rem;"
        >
          ← Back to projects
        </a>
      </div>
    </main>
  `);
}

Interactive pages

Export mount(shadow: ShadowRoot): () => void alongside render() for pages that need a live DOM. The router attaches a shadow root to #mount, calls mount(), and calls the returned cleanup on next navigation. Shadow DOM is isolated from global stylesheets and inject theme styles via <link> or ?inline import.

Top-level pages

  1. Create src/pages/<page>.ts with a render() export
  2. Add a route variant and case in src/router.ts
  3. Add a nav link in src/components/nav.ts if needed

Docker

docker build -t ehouse-io .
docker run -p 8080:80 ehouse-io

Multi-stage build: Node 22 compiles the site, nginx alpine serves dist/. nginx.conf adds the SPA fallback (try_files -> index.html) required for client-side routing because without it, direct URLs and refreshes on any route other than / return 404.