Why we moved our static sites from Hostinger to Cloudflare
Why Trebla replaced FTP deployments and mutable hosting directories with immutable Cloudflare Pages deployments at the edge.
- Cloudflare Pages
- Static sites
- Infrastructure
- Deployment
Why we moved our static sites from Hostinger to Cloudflare
The migration was less about page speed and more about removing a server from the path between a deploy and a traffic spike.
For years, several Trebla projects followed a familiar release path. GitHub Actions built the site, opened an FTP connection to Hostinger and copied the result into the production directory. The workflow was understandable, inexpensive and good enough for a small static site.
It also made every release depend on machinery the application did not need.
A static export is a directory of HTML, CSS, JavaScript and images. It has no long-running process to supervise. It does not need PHP or a traditional web server per project. Yet we were shipping those files through FTP credentials to a single hosting destination, then treating that destination like an application server.
We have been moving that class of project to Cloudflare Pages. The reason is blunt: Pages fits the workload better, fails in fewer interesting ways and gives a traffic spike less infrastructure to overwhelm.
The old deployment worked until it became part of the product
FTP deployment looks small in a workflow file. Install a client, authenticate, upload the build and verify the public URL. The complexity appears later.
The CI job needs a server address, username and password. It needs rules about which remote files to replace and which to preserve. An interrupted upload can leave the destination between two releases unless the workflow adds staging or atomic promotion. Verification has to determine whether the public site contains the new build rather than a partial copy, a cached response or the previous release.
We had tests around this deployment code because it had earned them. Our repositories checked for the expected FTP secrets and guarded the workflow that copied each static export to Hostinger. Openings also used the deployed site as a public bridge for generated editorial media. A website release and a social publishing run could now share the same failure domain.
None of these problems made FTP unusable. They made it expensive in attention. Every safeguard was code we had to keep, review and debug around a directory of static files.
That was the signal to change the hosting model.
A static site should end at the build artifact
Our preferred path is now shorter:
commit → build and checks → immutable deployment → Cloudflare edge
Cloudflare Pages can connect directly to GitHub or GitLab and deploy each push. Pull requests receive preview URLs, the commit receives a deployment check, and production keeps serving the last successful deployment when a new build fails. A completed production deployment also becomes a rollback target.
That changes the unit we operate. Under the FTP model, production was a mutable directory. Under Pages, production points to a completed deployment. We no longer need to reason about which files reached the server before a connection dropped. A failed build does not edit the active deployment, and a rollback selects an earlier release instead of reconstructing one through another upload.
The distinction is practical during an incident. We can ask which deployment is live and move production back to a known build. We do not need to ask whether two versions now coexist in the same remote directory.
Why it handles the rush better
When we say Cloudflare “handles the rush,” we mean something specific.
Pages uploads each site file to Cloudflare's globally distributed network. A request for an existing static asset does not wait for our own server process to read a file, compete for a shared machine or scale up. Cloudflare currently treats static asset requests on Pages as free and unlimited on both free and paid plans.
This architecture gives a static site a much wider margin during a sudden burst of visitors. The same landing page, documentation file or client-side application can be served from Cloudflare's network without us sizing a server for the busiest hour it may see all year.
We still avoid promises that a site “cannot go down.” Cloudflare can have incidents. DNS can be misconfigured. A build can ship a broken application. A third-party script can fail. The useful claim is narrower: traffic to static files no longer converges on a small hosting instance that we own and must provision.
That removes the bottleneck we were most likely to create ourselves.
The deployment model improved more than the response time
CDN latency is the visible benefit. Operations changed more.
Preview deployments give each pull request a real URL without touching production. Build failures stop before release. Successful deployments remain available for rollback. The Git provider records the result beside the commit. These are ordinary Pages features, so each Trebla project needs less private deployment code.
The credential boundary is smaller too. An FTP workflow needs credentials that can write into a production filesystem. A Git integration needs access to the selected repository and the Pages project. Cloudflare recommends limiting its GitHub application to the repositories it should build. We can reason about that permission as an integration, rather than passing a general-purpose file transfer password through every deployment job.
Less custom deployment code also means fewer project-specific variations. One repository should not delete remote files, another should preserve a media directory, and a third should recover after a half-finished upload. The site build owns the output. Pages owns publishing that output. Larger generated media can live in R2 with its own retention and verification rules instead of borrowing the website's production directory.
That separation has already shaped our work on Openings. Public editorial assets are moving to bounded R2 storage, with immutable object checks and explicit retention, while the website remains a static deployment. Publishing a social image no longer needs to behave like a website release.
Free has limits, and they are useful constraints
Pages is generous for our workload, but we design against its documented boundaries.
On the Free plan, Cloudflare currently allows 500 builds per month, one concurrent build, 20,000 files per site, files up to 25 MiB and 100 custom domains per project. Preview deployments are unlimited. Those numbers comfortably fit our static websites, but they rule out using Pages as an unexamined bucket for large media collections. We use R2 when an asset does not belong in the site build.
Dynamic execution has a different budget. Pages Functions count against Workers quotas; on the Free plan, Workers and Pages Functions share 100,000 requests per day. Adding a Function can also route requests through dynamic execution unless _routes.json excludes the static paths.
For that reason, we keep a sharp boundary between a static site and its dynamic dependencies. A marketing site can remain cheap and resistant to traffic spikes because most requests stop at static assets. Authentication, APIs, databases and server-side rendering require their own capacity plans.
Moving a frontend to Cloudflare does not rescue an API that falls over under load. It makes the remaining bottleneck easier to see.
We did not move everything into one vendor
Cloudflare Pages does not replace every service we previously associated with hosting.
Email is the clearest example. Cloudflare Email Routing can forward incoming mail, but it is not an IMAP mailbox. We can move DNS and the website while keeping mail with another provider. Transactional email also deserves separate credentials, delivery monitoring and failure handling.
The same rule applies to databases and long-running application servers. WordPress or a PHP application does not become a Pages project without an architectural change. A static frontend backed by an external API is only as available as the user journey's weakest required dependency.
This keeps the migration honest. We move the workloads that fit the edge model and leave the others where their operational needs can be met. “Cloudflare-first” is a default for static delivery, not an excuse to force every system into Workers.
Our default for new static projects
For a new Trebla landing page, blog, documentation site or statically exported application, Cloudflare Pages is now the default. The repository contains the source. CI proves the build. Cloudflare publishes the completed artifact and serves it from the edge. R2 owns public media that needs a lifecycle independent of the site.
Hostinger still has a place for workloads that need the hosting environment it sells. Our static sites did not. Keeping them there meant maintaining a server-shaped deployment for files that could be distributed directly.
The migration removed FTP secrets, mutable production directories and a single hosting destination from the request path. When the next project gets an unexpected wave of visitors, we want the interesting question to be what brought them there, not whether the web server can keep up.