On this page8 sections
A Framer site converted to Next.js with Deframer starts from the same assets, fonts and animations as the published site, so its performance should begin close to the original. To improve on that, measure the production build with Lighthouse or PageSpeed Insights and your own Core Web Vitals data, then work through the usual levers in a Next.js App Router project: font loading, image delivery, components that render only in the browser, third-party scripts carried over from custom code, and caching headers on your host. Change one thing at a time and measure again.
This post is a practical order of work. It doesn't promise numbers, because results depend on your site's content, your host and your visitors.
Step 1: Measure before you change anything
Set a baseline
- Build and run the production version locally:
npm run build, thennpm start. Never measurenpm run dev; development mode is much slower and unrepresentative. - Run Lighthouse in Chrome DevTools against the same pages on the published Framer site and on your local or staging build. Use the same device profile for both.
- Once deployed, check the pages in PageSpeed Insights. When your site has enough traffic, it also shows field data from real Chrome users, which matters more than any lab score.
- Write the three Core Web Vitals down per page: Largest Contentful Paint (LCP), Interaction to Next Paint (INP) and Cumulative Layout Shift (CLS).
Lab scores vary between runs. Run each test a few times and compare medians rather than single results.
Collect your own field data
Next.js can report Web Vitals from real visits. Add a small client component (a new file you create):
// src/app/web-vitals.tsx (new file)
"use client";
import { useReportWebVitals } from "next/web-vitals";
export function WebVitals() {
useReportWebVitals((metric) => {
// Send to your analytics endpoint instead of the console in production.
console.log(metric.name, Math.round(metric.value));
});
return null;
}Render <WebVitals /> inside <body> in src/app/layout.tsx. Check the Next.js docs for the current shape of the metric object before sending it anywhere.
Check how routes are rendered
The next build output lists every route and marks whether it is prerendered or rendered on demand. CMS detail pages in a converted project are prerendered from generateStaticParams, with dynamicParams = false, so they are served as static HTML. If you add code that reads cookies or headers in a route, it may become dynamic; the build output is where you'll notice.
Step 2: Fonts
By default, the converted project loads fonts from the same CDNs the published site uses. Each family gets an @font-face file in src/styles/fonts/, with font-display: swap, and the root layout connects early to those hosts:
<link rel="preconnect" href="https://framerusercontent.com" />
<link rel="preconnect" href="https://framerusercontent.com" crossOrigin="" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />Things worth trying, one at a time:
- Remove unused families. If a redesign dropped a font, delete its file in
src/styles/fonts/and its import. Faces that no text uses are usually not downloaded, but fewer rules are easier to reason about. - Preload the one font your hero text uses. A
<link rel="preload" as="font" type="font/woff2" crossOrigin="">for that file can help LCP when the largest element is text. Preloading too many files has the opposite effect. - Serve fonts from your own domain. Converting with "Include assets locally" puts the font files in
public/fonts/, which removes a connection to another host. Check your font licenses before self-hosting; the post on fonts and assets: CDN vs. local covers that choice.
Step 3: Images
The converted next.config.ts sets images: { unoptimized: true }. Images keep the responsive srcSet the published site uses, with width variants served by Framer's CDN:
srcSet: "https://framerusercontent.com/images/….svg?scale-down-to=512 512w, …?scale-down-to=1024 1024w, ….svg 1521w",That already gives browsers a choice of sizes. Your options from here:
Option | What changes | Trade-off |
|---|---|---|
Keep the CDN | Nothing | Images keep depending on Framer's CDN. |
Convert with local assets | Images and every | You serve them from your host; you are responsible for compression and cache headers. |
Adopt | You replace specific images by hand and configure remote patterns or local files | More code to maintain; worth it mainly for your largest, most visible images. |
Whatever you choose, look at the LCP element Lighthouse reports. If it's an image, make sure it isn't lazy-loaded and that its sizes value matches how wide it really displays.
Step 4: Components that render only in the browser
Code components that need the browser load after hydration (through next/dynamic), and the result page lists them under "Components that render only in the browser." Two checks help:
- Reserve their space. If one sits above the fold, give its container a fixed height or
aspect-ratioin the CSS module so the page doesn't shift when it mounts. That protects CLS. - Question heavy ones. WebGL backgrounds, 3D scenes and large sliders are often the biggest JavaScript on the page. If one is decorative, consider a static image on Phone, or loading it only when it scrolls into view.
Appear animations also affect perceived speed. If your hero heading fades in after a delay, LCP may land later than you expect. Shortening that delay is a design decision as much as a technical one; animation values live in the view files and play through motion/react, as the post on keeping Framer animations with Motion explains.
Step 5: Third-party scripts from custom code
Site and page custom code from Framer (analytics, chat widgets, tag managers) is carried over as real elements in the layout and routes. Each third-party script competes with your own code for the main thread, which often shows up as a worse INP.
- List every script in
src/app/layout.tsxand the route files. - Remove the ones you no longer use. Old tracking snippets are common.
- For the rest, consider moving them to
next/scriptwith a later loading strategy, such asafterInteractiveorlazyOnload, where the vendor supports it. - Re-measure INP and Total Blocking Time after each change.
Step 6: Caching headers on your host
Next.js serves its own build output under /_next/static/ with long-lived caching. Files in public/ are different: their names don't change when their contents change, so hosts are more careful with them. If you converted with local assets, you can set headers yourself in next.config.ts:
const nextConfig: NextConfig = {
// …the existing options
async headers() {
return [
{
source: "/fonts/:path*",
headers: [{ key: "Cache-Control", value: "public, max-age=31536000, immutable" }],
},
{
source: "/images/:path*",
headers: [{ key: "Cache-Control", value: "public, max-age=604800" }],
},
];
},
};Use immutable only for files you will rename whenever you change them. Some hosts apply their own caching rules on top of these, so check your provider's docs; the deployment guide covers the common hosts.
A short checklist
- Baseline the published site and the production build with the same tool and profile.
- Fix the LCP element first, then layout shifts, then interaction delays.
- Change one thing per commit and re-measure.
- Keep the original site's numbers in your notes, so you know when you've improved on it.
If a conversion didn't fit within the limits because of asset size, see the limits and retry with assets on the CDN.
FAQ
Will a converted site be faster than the Framer original?
It depends on the site and your host, so measure rather than assume. The converted project starts from the same assets and animations, and gives you direct control over fonts, images, scripts and caching from there.
Should I remove `images: { unoptimized: true }`?
Only if you also switch images to next/image. The converted code uses the published site's srcSet from the CDN, so the setting by itself changes little; plan the change image by image.
Do the preconnect links still matter with local assets?
Less so. Once fonts and images are in public/, the connections to the CDN hosts are needed only for what stays remote, such as fonts declared through Google Fonts CSS.
Which Core Web Vital should I fix first?
Start with whichever is worst in your field data. Without field data, LCP is usually the clearest first target because it reflects what visitors see first.
