On this page10 sections
A Framer to Next.js migration is ready to launch when every old URL answers on the new site with the same content, status code and metadata, assets and fonts load, CMS pages and forms work, npm run build passes, and you have a tested rollback. Work through the phases below in order: inventory, content, behavior, SEO, build, cutover, then monitoring. Each item says what to check, where, and how. Most of the checklist applies unchanged if you convert to TanStack Start, Astro, React Router or Vite instead.
This is a pre-launch checklist, not a walkthrough of the conversion itself. For the conversion steps, see how to export a Framer site to Next.js. File paths below refer to a project converted by Deframer; if you rebuilt the site by hand, the same checks apply, only the file names differ. Deframer is an independent tool and is not affiliated with, endorsed or sponsored by Framer.

Phase 1: what URLs does the site have today?
Start with a complete list of the live site's URLs, because every later check is run against it. The published sitemap is the quickest source, but it lists only pages, not every redirect or old URL that still gets traffic.
- [ ] Save the live sitemap:
curl -s https://www.example.com/sitemap.xml -o old-sitemap.xml. On a multi-locale site, also fetch eachsitemap_<code>.xmlit lists. - [ ] Extract the URLs into a plain list:
grep -o '<loc>[^<]*' old-sitemap.xml | sed 's/<loc>//' > urls.txt. - [ ] Add URLs the sitemap misses: pages you hid from search engines, the 404 page, and the redirects listed in Framer's site settings.
- [ ] Export the Pages report and the top pages from the Performance report in Google Search Console, and add any URL with impressions that isn't in
urls.txt. - [ ] Note each URL's form (with or without a trailing slash). The new site must answer at the same form; the other form should redirect to it.
- [ ] Compare
urls.txtwith the routes table in the converted project'sREADME.md. Every URL should map to a route file undersrc/app/.
A fuller audit, including traffic baselines and redirect maps, is in how to audit a website before migrating it to Next.js.
Phase 2: are content, images and fonts all there?
The new site should show the same content and load the same files, from wherever you decided assets should live. Decide that first: Framer's CDN, or local files in public/.
- [ ] Open every route with
npm run devand compare it with the live page. The compare view helps when live previews are available; otherwise use two browser windows. - [ ] Decide between "Assets on Framer's CDN" and "Assets in the project". See CDN vs. local assets for the trade-offs.
- [ ] If you want no dependency on Framer's CDN, check what still points at it:
grep -rl "framerusercontent.com" src public | head. - [ ] Open the Network panel on a few pages and filter by status: no request should fail with 404 or 403.
- [ ] Check fonts in
src/styles/fonts/<family>.css: each@font-facesrcshould load (DevTools, Network, Font). If you use a commercial font, confirm your license covers self-hosting or the new domain. - [ ] Check favicons and the social image: they're set in
src/app/layout.tsxand in each route'smetadata. - [ ] Revisit every notice on the result page, especially "Files that couldn't be downloaded". Common conversion issues explains each one.
Phase 3: do CMS pages, forms and integrations work?
CMS detail pages, forms and third-party scripts are the parts most likely to look fine and still be broken, so test them with real input.
CMS content and dynamic routes
- [ ] For each collection in
src/cms/, count the records in<collection>.jsonand compare with the number of items on the live site. - [ ] Open a few detail pages per collection, for example
src/app/blog/[slug]/page.tsxroutes, including the newest and oldest item. - [ ] Request a slug that doesn't exist:
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/blog/does-not-existshould print404, because detail routes setdynamicParams = false. - [ ] Check list pages for pagination, filters and sort order against the live site.
- [ ] Check rich text: headings, links, embedded videos and code blocks inside CMS items.
More on the CMS output in Framer CMS to JSON in Next.js.
Forms and integrations
- [ ] Copy
.env.exampleto.env.localand setFORMS_WEBHOOK_URL, or the per-formFORMS_<NAME>_WEBHOOK_URL. - [ ] Submit each form once with valid data and confirm the submission arrives at the webhook (Zapier, Make, Slack, your own endpoint).
- [ ] Submit each form with invalid data and confirm the error state matches the live site.
- [ ] Set the same webhook variables on your host. A variable only in
.env.localdoesn't reach production, and without one, submissions are only logged. - [ ] Check analytics, chat widgets and tag managers: they come over as custom code in the root layout or the page's route. Confirm each one records a visit from the preview deployment.
- [ ] List anything that depended on Framer's hosting (Framer analytics, Framer's form submissions view, A/B tests) and decide what replaces it.
Forms after leaving Framer covers webhook setups.
Phase 4: do animations and responsive layouts match?
Check the site at real widths and at each breakpoint boundary, not only at three common sizes.
- [ ] Read the
breakpointsmap at the top of a view insrc/views/and note the exact ranges (for example 810 to 1199px for Tablet). - [ ] Check each boundary on both sides (809 and 810, 1199 and 1200 with those ranges), then 1440, 768 and 390.
- [ ] Resize slowly across each boundary and look for layers that appear twice or disappear.
- [ ] Test on one real phone: the address bar, safe areas and font rendering differ from desktop emulation.
- [ ] Play every hover state, appear animation, page transition, ticker and scroll effect, on the production build too (
npm run build && npm start). - [ ] If the site honors reduced motion, turn on "Reduce motion" in your OS and reload.
- [ ] Watch for layout shifts as the page loads, especially around code components that appear after the page renders.
When something differs, fixing layout differences after converting Framer to Next.js goes through causes and fixes, and keeping Framer animations with Motion covers animation code.
Phase 5: are metadata, canonicals, redirects and the sitemap right?
Search engines should see the same titles, descriptions and canonical URLs as before, with the production domain in every absolute URL.
- [ ] Set
metadataBaseinsrc/app/layout.tsxto the production origin (for examplehttps://www.example.com), if it differs from the published URL. - [ ] View the source of a few pages and compare
<title>,<meta name="description">,<link rel="canonical">andog:tags with the live page. - [ ] Fill in metadata for any page listed under "Page metadata that couldn't be computed".
- [ ] For CMS detail pages, check that
metadata.jsonbeside the route has a title and description for each item. - [ ] Replace the published domain in
public/robots.txtand in every<loc>ofpublic/sitemap.xml(and per-locale sitemaps). They're copied as published, so they still name the old domain. - [ ] If you replace the static sitemap with a generated
src/app/sitemap.ts, deletepublic/sitemap.xmlfirst so the two don't conflict. See the Next.js sitemap docs. - [ ] Check
redirects()innext.config.tsagainst Framer's site settings, and add any redirect listed under "A redirect wasn't carried over". The Next.js redirects docs describe the format. - [ ] Make sure no
noindexis left on pages that should be indexed:curl -s https://preview.example.com/ | grep -i 'name="robots"'. - [ ] Check that
hreflangalternates are present on multi-locale pages.
SEO when moving off Framer explains each of these in detail.
Phase 6: does the production build pass and deploy?
A development server forgives things a production build doesn't. Build locally, then deploy to a preview URL before touching DNS.
- [ ] Check Node.js:
node -vshould report 20.9 or later for Next.js 16. - [ ] Clean install:
rm -rf node_modules && npm install, with no unresolved package errors. - [ ] Build:
npm run build. Read every warning, not only errors. - [ ] Type check:
npx tsc --noEmit. Projects with Framer code components keepignoreBuildErrors: trueinnext.config.ts, so the build passes even with type errors; know what's there. - [ ] Run the production server locally:
npm start, and click through the main pages. - [ ] Deploy to a preview URL on your host. See deploying a converted Framer site.
- [ ] Request every URL from Phase 1 on the preview and log the status codes:
while read -r url; do
path="${url#https://www.example.com}"
printf "%s %s\n" "$(curl -s -o /dev/null -w "%{http_code}" "https://preview.example.com$path")" "$path"
done < urls.txt > statuses.txt
cut -d" " -f1 statuses.txt | sort | uniq -c # count per status code
grep -v "^200 " statuses.txt # everything that isn't a 200- [ ] Every page should return
200; anything else needs a reason. - [ ] Check the other URL form of a page:
curl -sI https://preview.example.com/about/should show a308withlocation: /about(or the reverse, if your site uses trailing slashes). - [ ] Check a made-up URL returns the 404 page with a
404status, not a200. - [ ] Run PageSpeed Insights or Lighthouse on the preview for the home page and one heavy page, and compare with the live site. See Framer PageSpeed before and after conversion.
Phase 7: how do you cut over DNS safely?
Switch DNS only after the preview passes, and keep the Framer site published until the new site is confirmed live, so going back is one DNS change.
- [ ] Lower the TTL of the records you'll change a day or more ahead, so the switch spreads quickly. Google suggests lowering it in advance in its guide to moving hosts without URL changes.
- [ ] Add the domain (apex and
www) to the new host and confirm HTTPS certificates can be issued. - [ ] Note the current DNS records (screenshot or export) before changing anything.
- [ ] Change the records as your host instructs.
- [ ] Check from outside your network:
dig +short www.example.comandcurl -sI https://www.example.com/should show the new host's headers. - [ ] Confirm the apex and
wwwform redirect to the one you use. - [ ] Re-run the URL status loop from Phase 6 against the production domain.
- [ ] Submit a test form on production.
- [ ] Disconnect the domain from Framer only after traffic reaches the new host; check Framer's help center for the current steps.
Rollback plan
- [ ] Write down the old DNS records and who can change them.
- [ ] Keep the Framer site and plan active until the new site has been stable for a while. Before you cancel your Framer plan lists what to save first.
- [ ] Decide in advance what triggers a rollback (forms failing, key pages erroring, a spike in 5xx responses).
- [ ] To roll back: restore the old records. With a low TTL, most visitors are back on Framer within that TTL.
- [ ] Keep the previous production deployment on your host so a bad code change can be reverted without touching DNS.
Phase 8: what should you monitor after launch?
Watch Search Console, analytics and form deliveries for the first few weeks. Google notes that a crawl rate dip right after a move is normal, and that visibility can fluctuate temporarily.
- [ ] Submit the updated sitemap in Search Console (Sitemaps report).
- [ ] Use URL Inspection on the home page and a few key pages to confirm Google sees the new canonical and content.
- [ ] Check the Pages report weekly for new "Not found (404)" or redirect errors.
- [ ] Compare clicks and impressions for your top pages against the weeks before launch.
- [ ] Check your host's logs for 404s and 5xx responses on real URLs, and add redirects for old URLs that still get requests.
- [ ] Confirm analytics and form deliveries continue daily for the first week.
- [ ] Check Core Web Vitals in Search Console once enough field data has accumulated.
Copy the whole checklist
Paste this into a GitHub issue, Linear ticket or Notion page.
## Phase 1: URL inventory
- [ ] Save the live sitemap (and per-locale sitemaps)
- [ ] Extract URLs into urls.txt
- [ ] Add pages missing from the sitemap, the 404 page and Framer redirects
- [ ] Add URLs with impressions from Search Console (Pages, Performance)
- [ ] Note each URL's form (trailing slash or not)
- [ ] Map every URL to a route in the README routes table
## Phase 2: content, images and fonts
- [ ] Compare every route with the live page
- [ ] Decide: assets on Framer's CDN or local in public/
- [ ] grep -rl "framerusercontent.com" src public (if going local)
- [ ] No failed requests in the Network panel
- [ ] Fonts in src/styles/fonts load; license covers the new setup
- [ ] Favicons and social images set
- [ ] All result-page notices resolved
## Phase 3: CMS, forms, integrations
- [ ] Record counts in src/cms/*.json match the live site
- [ ] Detail pages render (newest and oldest item)
- [ ] Unknown slug returns 404
- [ ] List pagination, filters and sort order match
- [ ] Rich text (links, embeds) renders
- [ ] FORMS_WEBHOOK_URL set locally; each form delivers
- [ ] Invalid submissions show the right error state
- [ ] Webhook variables set on the host
- [ ] Analytics, chat and tag scripts record visits
- [ ] Replacements chosen for Framer-hosted features
## Phase 4: animations and responsive
- [ ] Note breakpoint ranges from src/views/*.tsx
- [ ] Check both sides of each boundary, then 1440 / 768 / 390
- [ ] No doubled or missing layers while resizing
- [ ] Test on a real phone
- [ ] Hover, appear, page transitions, scroll effects on the production build
- [ ] Reduced motion respected (if the site opts in)
- [ ] No visible layout shift on load
## Phase 5: SEO
- [ ] metadataBase set to the production origin
- [ ] Titles, descriptions, canonicals, og: tags match the live site
- [ ] Missing metadata filled in
- [ ] CMS metadata.json complete
- [ ] Domain replaced in public/robots.txt and public/sitemap.xml
- [ ] (Optional) src/app/sitemap.ts, with public/sitemap.xml removed
- [ ] redirects() in next.config.ts matches Framer's site settings
- [ ] No stray noindex
- [ ] hreflang present on multi-locale pages
## Phase 6: build and deploy
- [ ] node -v >= 20.9
- [ ] Clean npm install
- [ ] npm run build passes; warnings read
- [ ] npx tsc --noEmit reviewed
- [ ] npm start and click through
- [ ] Deploy to a preview URL
- [ ] Every URL in urls.txt returns 200 on the preview
- [ ] Other URL form returns 308 to the right form
- [ ] Made-up URL returns 404
- [ ] PageSpeed compared with the live site
## Phase 7: DNS cutover and rollback
- [ ] TTL lowered in advance
- [ ] Domain (apex + www) added on the new host, HTTPS ready
- [ ] Old DNS records saved
- [ ] Records changed
- [ ] dig / curl -sI confirm the new host
- [ ] Apex/www redirect correct
- [ ] URL status loop re-run on production
- [ ] Test form submitted on production
- [ ] Framer domain disconnected only after traffic moved
- [ ] Rollback triggers and steps written down
- [ ] Framer site and plan kept active for now
- [ ] Previous deployment kept on the host
## Phase 8: monitoring
- [ ] Sitemap submitted in Search Console
- [ ] URL Inspection on key pages
- [ ] Pages report checked weekly for 404s and redirect errors
- [ ] Clicks and impressions compared with pre-launch
- [ ] Host logs checked for 404 and 5xx
- [ ] Analytics and forms confirmed daily for a week
- [ ] Core Web Vitals checked once field data existsFAQ
How long does a Framer to Next.js migration checklist take to complete?
It depends mostly on page count and how many forms, CMS collections and integrations the site has. A small marketing site can be checked in an afternoon; a site with large collections and several locales takes longer because of the per-route and per-item checks.
Do I need to keep my Framer plan after moving to Next.js?
Keep it until the new site has been live and stable for a while, so you can roll back with a DNS change. After that, see before you cancel your Framer plan for what to save.
Should I change URLs during the migration?
No. Keep every URL identical at launch and restructure later as a separate change with redirects, so any ranking change can be traced to one cause.
Why does my sitemap still show the old domain?
A converted project copies public/sitemap.xml and public/robots.txt as the site published them. Replace the domain in both, or generate the sitemap from src/app/sitemap.ts and delete the static file.
What should I check first after the DNS switch?
Confirm the domain serves the new host (curl -sI), re-run the URL status loop, submit a test form, then submit the sitemap in Search Console.
