---
title: charleliemorineau.com — a personal website without a framework
---

## Context

My former PHP website was no longer maintained. I wanted to replace it with a site meeting four requirements:

- **readable in five seconds** by a recruiter: name, role, experience and contact visible without scrolling;
- **editable without technical skills**: changing a text should require neither a build nor a database;
- **hostable anywhere**: classic shared hosting (Apache + PHP) must be enough;
- **secure by default**, since security is my job.

## Architecture

```mermaid
flowchart LR
  subgraph edit["Editing"]
    md["Markdown files<br/>content/fr · content/en"]
  end
  subgraph tools["Local tools (Node)"]
    gen["tools/"] --> og["Share images"]
    gen --> qr["QR code"]
  end
  subgraph host["Shared hosting: Apache + PHP"]
    pages["Static pages<br/>/ · /cv · /projets · /blog"]
    api["api/contact.php"]
  end
  md --> pages
  md --> gen
  visitor(("Visitor")) -->|"HTTPS + strict CSP"| pages
  visitor -->|"signed token"| api
  api --> mailbox[("Mailbox")]
```

A small script turns the Markdown content into complete HTML pages, readable by search engines and AI crawlers without running any JavaScript. In the browser, the same rendering code takes over for animations and interactivity. Publishing a text means editing a `.md` file, running the script and pushing: deployment is automatic.

## Technical choices

| Need | Choice | Why |
| --- | --- | --- |
| Editable content | Markdown + home-made parser | No CMS, no database, no build |
| Interface | Native HTML, CSS and JavaScript | Zero dependencies, fast, long-lasting |
| Hosting | Shared Apache + PHP | Minimal cost; `.htaccess` for caching and security |
| Contact form | Dependency-free PHP | Signed token, rate limiting, address never exposed |
| Diagrams | Self-hosted Mermaid | Only loaded on pages that contain diagrams |
| Previews, QR | Local Node scripts | Always in sync with the content, nothing extra to deploy |

## Security

- **Strict content policy** (`script-src 'self'`): no inline script, no third-party resource, self-hosted font.
- **Headers**: HSTS, `X-Frame-Options`, `Permissions-Policy`, `Cross-Origin-*` isolation.
- **Markdown rendered without `innerHTML`**: any HTML present in the content is never interpreted.
- **Contact form**: single-use HMAC token, origin check, honeypot, rate limiting by hashed IP, non-injectable mail headers. The destination address only exists in the server configuration.

## Outcomes

- 0 production dependency, 0 third-party request;
- updating content = editing a text file.
<!-- Put back if the /stack/ page is published:
- performance metrics and security headers visible **live** on the [Stack](stack/) page.
-->
