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

NeedChoiceWhy
Editable contentMarkdown + home-made parserNo CMS, no database, no build
InterfaceNative HTML, CSS and JavaScriptZero dependencies, fast, long-lasting
HostingShared Apache + PHPMinimal cost; .htaccess for caching and security
Contact formDependency-free PHPSigned token, rate limiting, address never exposed
DiagramsSelf-hosted MermaidOnly loaded on pages that contain diagrams
Previews, QRLocal Node scriptsAlways 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.