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
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.