← Projects

Website with its own admin panel and continuous deployment

Astro generating static pages, a PHP/MySQL API with a React admin panel, and publishing that triggers the build on its own through GitHub Actions.

AstroReactPHPMySQLGitHub Actions
Status
In production
Year
2026
Context
Own product, live
Live
Open →
Push → live
Deploy with no intervention
~1 MB
JS cut per page in the first stage
Website with its own admin panel and continuous deployment

The problem

I needed a site that would be found in search and, at the same time, could be updated by someone who does not open a code editor. A static page solves the first half and blocks the second; a dynamic admin panel solves the second and usually ruins the first.

What I built

  • Static pages generated at build time so they get indexed, with a React application on top for navigation.
  • My own PHP and MySQL API with an admin panel: item registration with photo upload, a blog and a record of contacts.
  • Publishing with no manual build. Publishing an item in the panel fires a webhook that triggers GitHub Actions, which rebuilds the static pages and sends them over FTP. Nobody needs to know a build exists.
  • A public catalogue endpoint, consumed today by another system of mine, the CRM, which mirrors the items instead of keeping a parallel registry.

Technical decisions

Fixing image orientation on upload. Phone photos store rotation in EXIF metadata. The resizing library ignores that metadata, so thumbnails came out sideways even though the original looked right on the computer. The fix rotates the pixels according to the EXIF before resizing, covering all eight possible cases, and rewrites the file without the metadata. I also wrote an idempotent maintenance script to fix what was already on the server.

One registry, with an explicit contract. When the CRM needed the same items, the easy way out was to duplicate the table. Instead, the public endpoint became a contract: the item is registered on the site and the CRM only reads. Renaming a field there breaks the other system, and that is written in the documentation on both sides, because a second source of truth only charges you months later.

How I verified it

Diagnosing performance with a number, not a hunch. The complaint was “the site feels slow”. Measuring an item page, it was around 4.3 MB of JavaScript, and most of that was a compiler running in the browser to transform the code on every visit: the page only became interactive some four seconds after loading. I split it into two causes by impact and attacked the smaller one first, because it was the low-risk one: the internal pages were loading the development build of the library instead of the production build. Switching it cut around 1 MB per page and was verified live in the browser, with the page rendering correctly and no console errors.

The definitive fix is the other one, compiling at build time and removing the compiler from the browser, and it is planned and has not landed yet. I record it this way because measuring and prioritising is not the same as having solved it, and the estimated gain only counts once it is live.

The incident, and the residue the first cleanup missed. The site was defaced through a compromise of the shared hosting account, by way of abandoned third-party sites on the same account, not through the site’s code. The response is documented in the repository: identifying the vector, rotating every credential, a clean redeploy, scanning and removing the malicious files, and hardening headers.

Weeks later, looking at search metrics, I found residue from the same attack: a folder that answered one thing to the search engine’s crawler and another to the visitor, and that had been injecting spam URLs into the index since before the defacement showed up. It was handled with an explicit content-removed response and a removal request in the search console. The lesson that stuck is not a technical one: “clean” has to be verified with evidence, not assumed because the symptom went away.