Host a Framer site on GitHub Pages

Convert a Framer site to a static Vite project, build it, push it to GitHub and deploy with Actions. Also works on Netlify, Cloudflare Pages and S3.

Published
Reading time
8 min
Deframer cover: “Host it free. Push to Pages.” Beside it, four tiles, Convert, Build, Actions and Pages, with Pages selected
On this page12 sections

You can host a Framer site on GitHub Pages, but not by uploading it from Framer, which has no code export. Convert the published site with Deframer to the Vite target, which builds to a static site with no server, run npm run build, and publish the dist/ folder with a GitHub Actions workflow. Pages serves it like any static host, with a custom domain if you want one. The Next.js, TanStack Start, Astro and React Router targets need a Node.js server, so Vite is the one to pick for GitHub Pages.

The same project also deploys unchanged to Netlify, Cloudflare Pages, Vercel or an S3 bucket behind a CDN, so the build step below is worth learning once.

Why the Vite target

GitHub Pages serves files. It doesn't run Node.js, so it can't host anything that needs a server at request time. Deframer converts a Framer site into one of five projects, and they differ here:

Target

What npm run build produces

Fits GitHub Pages

Vite

Every page prerendered to HTML in dist/; no server

Yes

Astro

Prerendered pages, run by the project's own server.ts

No, it needs Node.js

React Router

Prerendered pages, run by the project's server.ts

No, it needs Node.js

TanStack Start

A server build

No

Next.js (default)

A server app

No; see below

For Next.js, the framework can produce a static export, but it doesn't support redirects in next.config or route handlers that read the request. A converted project uses both, for redirects and forms, so you'd have to move those elsewhere first, and Deframer doesn't do that for you. We cover the trade-off in Framer to HTML or Next.js. If you want a site on GitHub Pages, converting to Vite from the start is far simpler.

How the Vite project works: it uses React Router's data router in the browser, and a prerender script writes every page into dist/ at the file a static host serves for its URL (/about becomes about.html, / becomes index.html). Each page is styled before its scripts run; the browser then hydrates it, and links move between pages without a full reload, as on the published site. There's no Node.js in production.

Before you start

  • A site you own, or have permission for. Deframer downloads are for your own Framer site, or one whose owner published your verification tag. See verifying Framer site ownership.
  • A GitHub account and a repository.
  • Node.js 22.18 or later locally. The project's prerender script runs as TypeScript, which Node 22.18 can execute directly.
  • A form service, if the site has forms. Static sites have no server to receive form posts. More on this below.

Step 1: Convert to Vite

  1. Paste your published Framer address into the convert form on deframer.co. Converting is free.
  2. Choose Vite as the target.
  3. Decide on assets. By default, images, fonts and videos keep loading from Framer's CDN. Choosing Assets in the project downloads them into public/, so the site doesn't depend on Framer at all. For a site you're moving off Framer, the second is the cleaner exit; Assets on the CDN or in the project explains the choice.
  4. Verify ownership with the one-line meta tag Deframer shows, publish your Framer site, and download. A verified download costs 1 credit (10 without verification); see pricing.

Step 2: Build it locally first

Unzip the project and run it before pushing anything.

sh
unzip my-site.zip
cd my-site
npm install
npm run build        # prerenders every page into dist/
npm run preview      # serves dist/ the way a static host does

Open the address npm run preview prints and click through the site. The preview server mimics a static host: each page's file at its URL, the site's redirects, and the site's 404 page with a 404 status at unknown URLs. If something looks wrong here, it will look wrong on Pages, and it's easier to diagnose locally. Common Framer-to-Next.js conversion issues lists the usual suspects, most of which apply to every target.

Step 3: Create the repository

sh
git init
git add .
git commit -m "Initial conversion from Deframer"
git branch -M main
git remote add origin git@github.com:your-name/your-site.git
git push -u origin main

Commit package-lock.json too, so the workflow installs exactly what you tested. The project's .gitignore already excludes node_modules, dist and local .env files. If you plan to keep editing in Framer and convert again, read maintaining a converted Framer site with Git before you make your own changes on main.

Step 4: Set the repository to deploy with Actions

In the repository on GitHub, open Settings, then Pages, and set the source to GitHub Actions. Then add this workflow as .github/workflows/deploy.yml:

yaml
name: Deploy to GitHub Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run build
        env:
          VITE_FORM_ENDPOINT: ${{ vars.VITE_FORM_ENDPOINT }}
      - uses: actions/upload-pages-artifact@v3
        with:
          path: dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4

This is a standard Pages workflow with the project's build in the middle, so check GitHub's current documentation for the latest action versions. Commit it and push. The first run builds the site and publishes dist/; later pushes to main redeploy it.

If the site has no forms, delete the env lines. If it does, add the endpoint as a repository variable under Settings, then Secrets and variables, then Actions, then Variables.

Step 5: Forms on a static host

A static site can't receive a form post, so each native Framer form in a Vite project posts its fields, as JSON, to a form service you choose: Formspree, Basin, Getform, Web3Forms and similar. Create a form there and give the project its URL at build time:

  • VITE_FORM_ENDPOINT is used for every form.
  • VITE_FORM_<NAME>_ENDPOINT, for example VITE_FORM_CONTACT_ENDPOINT, is used for one form. The project's README lists each form and its variable name.

Vite writes the URL into the build, so it's public, and changing it needs a new build. That's why the workflow passes it into npm run build. If none is set, the form shows its error state, and the browser's console names the variable to set. The service handles spam filtering, email and webhooks. A submission that fills the form's hidden honeypot fields isn't sent. More background is in forms after leaving Framer.

Step 6: Custom domain

GitHub Pages publishes at https://<your-name>.github.io/ for a user site, or https://<your-name>.github.io/<repository>/ for a project site. The generated Vite project builds for the root of a domain; its configuration doesn't set a subfolder base path, so serve it from a root address:

  • A custom domain on any repository, or
  • A user or organization site, a repository named <your-name>.github.io.

Avoid the /<repository>/ form unless you've changed the project's base path and checked every link and asset yourself.

For a custom domain:

  1. Under Settings, then Pages, enter your domain. Add a file named CNAME containing the domain to the project's public/ folder so every build includes it.
  2. At your DNS provider, point the domain at GitHub Pages, using the records GitHub's documentation lists for an apex domain or a www subdomain.
  3. Wait for DNS to settle, then turn on Enforce HTTPS in the Pages settings.

Before you switch a live domain over, lower the DNS TTL in advance and compare the new site against the old one. Website migration audit checklist and SEO when moving off Framer cover the redirect and indexing side.

What to know about Pages and a converted site

  • Clean URLs work. Pages serves about.html at /about, which is how the converted site's URLs are laid out.
  • Redirects. GitHub Pages has no redirect settings. The project writes a small page at each redirected URL (public/old.html for /old) that sends the browser on, so redirects work, but as page loads rather than server 301s. If you need real HTTP redirects, a host with redirect rules, such as Netlify or Cloudflare Pages, is the better fit. The project includes public/_redirects for those.
  • 404 pages. Pages serves the site's 404.html at unknown URLs. At the 404 page's own address, /404, Pages answers 200 rather than 404, because it has no setting for that.
  • New CMS items. Every CMS item known when the site was converted has its page. One added in Framer afterwards doesn't until you convert again. See Framer CMS to JSON in Next.js for how the data is stored.
  • Split tests don't run. A static site has no server to pick a variant, so every visitor sees each tested page's original.
  • Limits and terms. GitHub documents size limits, bandwidth limits and acceptable-use rules for Pages. Read them if the site is large or commercial.

Other static hosts

The same dist/ folder works elsewhere, with a one-line change in each case:

  • Netlify. Build command npm run build, publish directory dist. The project includes netlify.toml and _redirects. You can also point the form variable at netlify, so Netlify Forms receives submissions.
  • Cloudflare Pages. Same command and directory; it reads _redirects.
  • Vercel. The project includes vercel.json for clean URLs and redirects.
  • S3 behind a CDN. Upload dist/; you'll need to set up clean URLs and the 404 page yourself.

For Node.js hosts and Next.js specifically, deploying a converted Framer site to Vercel, Netlify, Cloudflare or Fly.io has the steps. If you're comparing the cost of hosting a Framer site yourself with Framer's, Framer hosting vs self-hosting costs lays out the numbers.

Keeping it updated

When you change the site in Framer and republish, convert it again, put the new output on its own branch and merge it, then push. The workflow rebuilds and redeploys. Re-converting is free and a new download uses a credit; credits never expire.

Moving client sites? The affiliates program pays 50% of each sale you refer.

Deframer is an independent tool and isn't affiliated with Framer or GitHub.

FAQ

Can I host a Framer site on GitHub Pages?

Yes, once you have its code. Framer itself can't publish to GitHub Pages and has no code export. Convert the published site to the Vite target with Deframer, build it, and deploy the dist/ folder with a GitHub Actions workflow.

Why does it have to be Vite?

GitHub Pages serves static files. The Vite target prerenders every page into dist/ and needs no server. Next.js, TanStack Start, Astro and React Router projects rely on a Node.js server, so they don't suit Pages.

Do forms work on GitHub Pages?

Yes, through a form service. Set VITE_FORM_ENDPOINT to the service's URL when you build, and each form posts its fields to it.

Can I use my own domain?

Yes. Add your domain under Pages settings, put a CNAME file in public/, point DNS at GitHub, and enable HTTPS. Serve the site from a domain root rather than a /<repository>/ path.

How much does it cost?

Converting is free. A verified download costs 1 credit ($10 for a single credit, $25 for 5, $100 for 30), or nothing per site with the $397 lifetime deal. GitHub's own pricing for Pages is on GitHub's site.

Keep reading

All posts