Personal portfolio and open-source presence of Jonas Pfalzgraf (JosunLP) — a static, bilingual (German/English), privacy-first website built with bQuery.js, TypeScript, and Tailwind CSS.
- Present Jonas as a full-stack developer and open-source maintainer
- Showcase curated open-source projects, with bQuery.js as the flagship
- Publish a Markdown blog that works on plain static hosting — new posts can be uploaded without rebuilding the site
- Ship zero trackers, zero third-party requests, and WCAG 2.2 AA as the accessibility target
| Concern | Choice |
|---|---|
| Framework | bQuery.js (component, reactive, i18n, a11y, security, media) |
| Language | TypeScript (strict) |
| Styling | Tailwind CSS 4 with a design-token layer |
| Build | Vite (assets) + custom prerender pipeline (HTML) |
| Markdown | marked + bQuery allow-list sanitization |
| Highlighting | highlight.js core (blog chunk only) |
| Tests | Vitest (+ axe-core for accessibility) |
See docs/architecture/architecture.md for the full picture.
- Bun ≥ 1.2 (runtime, package manager, and TypeScript
runner —
bun.lockcommitted)
bun installbun run dev # dev server at http://localhost:5173/Pages are rendered on the fly through Vite's SSR pipeline; client islands and styles are served by the regular Vite dev server.
The dev server rewrites canonical, hreflang, og:url and the JSON-LD
@ids to the origin that served the request, so Lighthouse and similar
tools audit the preview instead of reporting "canonical points to a
different domain" for every page. Two caveats remain, both properties of
the preview rather than the site:
- Forwarded ports must be public. A private Codespaces port answers
/site.webmanifestand/robots.txtwith a 302 togithub.dev, which surfaces as a CORS error in the console and an invalidrobots.txtin the report. - Dev-server performance is not production performance. Modules are
unbundled and unminified with no compression. Measure performance
against
bun run build && bun run preview— but note thatpreviewserves the real build, whose canonical URLs correctly namejosunlp.de. A canonical finding there is the audit doing its job, not a defect.
So: audit SEO against bun run dev, and performance against
bun run preview. Restart the dev server after pulling changes to it —
the origin rewrite only applies to a server started afterwards.
bun run build # social card + manifest + sitemaps + assets +
# prerendered HTML + precompression → dist/
bun run preview # serve dist/ locallyThe build fails on invalid project data or broken blog content — by design.
Two build steps produce artefacts worth knowing about:
generate:og-imagedrawspublic/og-image.png(1200×630) from the real logo geometry and the design tokens, with no image toolchain — see scripts/generate-og-image.ts.precompresswrites maximum-quality.brand.gzsiblings for every text file indist/, so a static host serves compressed bytes without spending CPU per request. Uploading them is optional.
bun run typecheck # strict TypeScript
bun run lint # ESLint (typed rules)
bun run format:check # Prettier (print width 80)
bun run test # unit + component tests (Vitest)
bun run test:a11y # axe-core checks on rendered pages
bun run validate # dist/ link, metadata, sitemap, robots checks
bun run check # all of the above + buildPosts are Markdown files with YAML front matter under
content/blog/{de,en}/. Regenerate the manifest, the blog sitemap, and
the per-locale Atom feeds after changes:
bun run generate:blog-manifest
bun run generate:blog-sitemap
bun run generate:blog-feedsArticles link to their newer and older neighbour, so publishing a post also changes the previously newest one — rebuild when that matters.
Posts can be published without a rebuild by uploading the Markdown
file and the regenerated index.json, blog-sitemap.xml, and
{locale}/blog/feed.xml to the server — the full procedure is documented
in docs/blog-content-workflow.md.
dist/ is plain static output deployable to any web server. Host
configuration examples (Apache/nginx), the blog URL rewrite, and security
headers are documented in docs/deployment.md and
docs/security-headers.md.
There are no environment variables. Site-wide values (origin, owner contact data, navigation, storage keys) live in src/app/configuration.ts.
- Implementation plan
- Architecture
- Blog content workflow
- Deployment
- Security headers
- Owner actions required
- Implementation report
MIT — see LICENSE.