You picked one. Six months later, the world’s changed: the static site needs a /contact form that emails you, or the web service is paying for an instance to serve mostly static files. This step is the playbook for migrating.
The immutable rule that shapes every migration
The same rule covered in the advanced Blueprint patterns tutorial, here in its frontend-shaped form. Plan migrations with that rule in front of you - it decides the shape of every option below.
Path 1: Static site → static site + new API
The most common migration. You shipped a static SPA, and now you need a few server-side endpoints - a contact form, an auth callback, a Stripe webhook receiver.
Don’t migrate the static site to a web service. Add a separate web service alongside it.
flowchart LR before["Before:<br/>my-frontend (static)"] after1["After: my-frontend (static)"] after2["After: my-api (web service)"] before --> after1 before --> after2
In the Blueprint:
services: - type: web runtime: static name: my-frontend buildCommand: npm ci && npm run build staticPublishPath: dist envVars: - key: VITE_API_URL value: https://my-api.onrender.com
- type: web name: my-api runtime: node plan: starter buildCommand: npm ci && npm run build startCommand: npm start healthCheckPath: /health envVars: - key: ALLOWED_ORIGIN value: https://my-frontend.onrender.comSync the Blueprint, the new API service spins up, the frontend rebuilds with VITE_API_URL baked in, browser calls hit the new API. No downtime, no DNS changes for existing static-site users.
This is the hybrid pattern by another name - most teams arrive at it through this exact migration.
Path 2: Web service serving static files → split into static + API
Going the other way. You started with a web service that serves both your built React app and an API. It works, but you’re paying for an instance that’s mostly serving static files, and free-tier cold starts are hurting first-load times.
Split it. The frontend becomes a static site (free CDN, no cold starts, free custom domains), the API stays as a web service.
flowchart LR before["Before:<br/>my-app (web service)<br/>serves React + /api/*"] after1["After: my-frontend (static)"] after2["After: my-api (web service)<br/>only /api/*"] before --> after1 before --> after2
The migration steps:
- Strip the API routes out of the frontend repo Or split into a monorepo with an
apps/weband anapps/apifolder. ThebuildCommandon the static site only builds the frontend; the API service builds and runs only the API. - Add the static site to the Blueprint A new service:
runtime: static,staticPublishPath: dist, the SPA fallback rewrite. Don’t touch the existing web service yet. - Verify the static site at its new URL Open
my-frontend.onrender.comand confirm pages load and API calls (still pointing at the old web service URL) work. - Cut over DNS or update VITE_API_URL Either repoint your custom domain to the static site, or swap
VITE_API_URLin the static site to point at the renamed API service. - Slim down the old web service Remove the static-file-serving routes. The web service becomes API-only.
The win, in concrete numbers: the static frontend gets free bandwidth from the CDN (counted against your workspace’s monthly free amount), no cold starts, and free custom domains. The API service can drop to a smaller instance because it’s no longer serving asset bytes.
Path 3: Static site → SSR web service
Less common, but it happens. You started with a Vite SPA, and now you need full server-side rendering for SEO, social previews, or per-request auth on every page.
This is the case where you genuinely replace the static site with a web service. The migration:
- Rewrite (or restructure) the frontend as an SSR-capable framework Next.js with
next start, Nuxt with the Node adapter, SvelteKit with the Node adapter, Remix, etc. - Add the new web service to the Blueprint A different service,
type: web,runtime: node, with astartCommandlikenpm start. Differentnamethan the old static site. - Verify the new web service at its onrender.com URL Confirm pages render, the API behaves correctly, and your auth flow works server-side.
- Repoint your custom domain From the static site to the new web service. The old static site becomes a
*.onrender.com-only fallback you can delete after a week. - Delete the static site Once the custom domain is on the new web service and you’re confident, remove the static-site service from the Blueprint.
Two things to expect:
- Cold starts are back. Even on paid plans, an SSR web service has a process to start. Plan for it in your perceived performance budget.
- Bandwidth costs change shape. You’re now paying for the web service’s bandwidth, not the static site’s. Usually similar; sometimes more.
Free-tier traps to remember
A condensed list of the gotchas that pop up most often in #help-render:
| Trap | Where it bites |
|---|---|
| Free web services spin down after 15 min idle | First request after sleep is a cold start; scheduled hits to keep it warm work but feel like a hack |
| Free web services don’t get custom domains | Want mysite.com? Either ship it as a static site (free) or pay for a non-free instance type |
| Static sites have no env vars at runtime | VITE_API_URL is baked at build; changing it requires a rebuild |
| Static sites can’t reach the private network | Browser → API hits public URLs only. CORS is required |
runtime is immutable | Every migration is a new service alongside the old, not an in-place flip |
| Build minutes count against the workspace, not per service | A monorepo with 5 frontends doesn’t get 5× the minutes |
| Bandwidth caps are workspace-wide | Heavy traffic on one service eats the budget for the whole workspace |
The first two cover most of “wait, why is my free-tier site behaving weirdly” tickets. The middle three cover most of “the migration broke something” tickets. The last two are the ones that surprise teams who scaled up to many services.
Where to go next
You now have the full picture for picking and migrating between Render’s two main service shapes. Natural next stops:
- Advanced Blueprint patterns - the cookbook of patterns for multi-service apps. Projects + environments, env groups, cross-service wiring, previews, monorepos, disks, scaling, and Docker/image runtimes.
- Render Workflows quickstart - when “I need code that runs in response to events” turns into a fan-out, retry-aware function rather than a web request.
- The Render docs - static sites, web services, and free-tier behavior - the source of truth for any field you don’t recognize.
What you learned
- Static site needs an API endpoint? Add a new web service alongside it. Don't try to migrate the static site
- Web service serving lots of static files? Split into static + API for a free CDN and no cold starts
- Going to SSR? It's a real replacement - new web service, repoint DNS, delete the static site once verified
- `runtime` is immutable. Every migration is a new service alongside the old, never an in-place flip
- Free-tier traps: services sleep, no custom domains on free web services, no runtime env vars on static sites, no private network access for static sites