Moving off WordPress to Webflow or Next.js: SEO and AI checklist
To move off WordPress without losing search traffic, save every URL, title and description before you touch anything, keep your slugs, and 301 every URL that has to change. Then rebuild what your SEO plugin did for you: schema, sitemap, canonicals and alt text. Webflow and Next.js both send real HTML, so AI crawlers can read the new site. The risk is in the URLs and the details the plugin used to handle.
Webflow or Next.js: the URL question
Pick the platform after you look at your current URLs, because it decides how many redirects you need.
- Webflow puts every CMS item under its Collection's slug. A post lives at
/blog/payroll-guide, and Webflow says "the Collection slug is an integral part of the Collection page path, and cannot be removed" (Webflow: CMS Collections). If your WordPress posts sit at the root, like/payroll-guide/, every post URL changes and needs a redirect. - Next.js lets you match any path. A route at
app/[slug]/page.tsxcan serve/payroll-guide/exactly as before.
Check your structure in WordPress under Settings, then Permalinks. "Post name" gives /sample-post/. A custom structure such as /blog/%postname%/ maps cleanly to a Webflow Collection with the slug blog.
Both are fine choices. Webflow suits teams where marketing edits pages without engineers. Next.js suits teams that want the site in the same codebase as the product.
Step 1: Record the old site before you change it
You cannot check what you did not write down. Do this while WordPress is still live.
List every URL. WordPress 5.5 and later serves a sitemap index at /wp-sitemap.xml. Yoast SEO turns that off and serves its own (WordPress core: XML sitemaps). Look for the Sitemap: line in your robots.txt to find yours. A sitemap index links to more sitemaps, so read both levels:
curl -s https://paidwick.com/wp-sitemap.xml \
| grep -o '<loc>[^<]*' | sed 's/<loc>//' \
| while read -r map; do
curl -s "$map" | grep -o '<loc>[^<]*' | sed 's/<loc>//'
done > urls.txt
Add the landing pages from your analytics and the Pages tab of the Performance report in Search Console. Do not forget category, tag and author archives, attachment pages, and /feed/. They often get links.
Save every title and description from the live HTML. Whatever your SEO plugin output is what Google has been showing.
while read -r url; do
html=$(curl -s "$url")
title=$(printf '%s' "$html" | grep -o '<title>[^<]*' | head -1 | sed 's/<title>//')
desc=$(printf '%s' "$html" | grep -o '<meta name="description" content="[^"]*"' | head -1 | sed 's/.*content="//; s/"$//')
printf '%s\t%s\t%s\n' "$url" "$title" "$desc"
done < urls.txt > seo-before.tsv
Save a schema sample. Open a post, a page and the home page, view source, and copy each application/ld+json block. You will rebuild these in Step 6.
Step 2: Export the content
WordPress's Tools, then Export, screen creates a WXR file, an XML file that contains "your posts, pages, custom post types, comments, custom fields, categories, tags, custom taxonomies, and users" (WordPress: Tools Export screen).
For Webflow, convert the export into one CSV per Collection, such as posts, categories and authors. Webflow says "you will need to restructure your data so that you can create one CSV file per Collection." Other notes from Webflow's migration guide:
- Images import from public URLs. Webflow downloads them and stores them on its CDN. It supports JPEG, GIF, PNG, WebP and AVIF up to 4MB. Keep WordPress online until the import finishes, so those URLs still work.
- Code embeds in rich text can be cleaned out on import. Webflow recommends you "QA these items to ensure that any custom code has not been sanitized."
- An item slug can be up to 256 characters.
- To keep original publish dates, add a custom date field.
- The easiest way to get the CSV format right is to create a few items by hand, export the Collection, and copy its columns.
For Next.js, convert posts to Markdown or MDX files, or load them into a headless CMS. Keep each post's slug, date, title and description as fields, because you will need all four.
Step 3: Keep your slugs
Use the WordPress slug as the new slug, character for character. Every slug you keep is a redirect you do not need.
On Next.js, also match the trailing slash. WordPress permalinks usually end in /. Set trailingSlash: true in next.config.js so /payroll-guide/ stays /payroll-guide/.
Step 4: Redirect everything that changes
On Webflow
Add redirects in Site settings, one by one or in bulk with a CSV file. Rules from Webflow's SEO migration guide and its redirect update:
- A CSV import overwrites your existing redirects. It does not add to them. Export first and merge.
- Wildcards use
(.*)and%1.(.*)in the old path captures part of the URL, and%1puts it back in the new path./resources/(.*)to/blog/%1moves a whole folder. - No regex and no rule order. You cannot exclude one page from a wildcard.
- Keep the total low. Webflow has no hard limit but recommends "1,000 maximum as best practice," because each rule adds weight to a file browsers download.
- Use relative source paths, like
/payroll-guide, not full URLs. - Free up the path first. To redirect a URL that a live page still uses, delete the page, save it as a draft, or change its slug.
- Localized paths need their own rules.
/old-urland/es/old-urlare two redirects.
A redirect CSV for root posts moving under /blog:
/payroll-guide,/blog/payroll-guide
/contractor-tax-forms,/blog/contractor-tax-forms
/category/guides,/blog
/author/roshan,/about
You cannot write a wildcard that catches "any root path except my pages." That is why moving many root posts to Webflow means one rule per post. Plan for it before you have 2,000 posts.
On Next.js
Permanent redirects go in next.config.js and return a 308 (Next.js: redirects). Google treats 301 and 308 as permanent (Google: redirects).
// next.config.js
const legacyIds = require('./legacy-ids.json') // [{ "id": "123", "slug": "payroll-guide" }]
module.exports = {
trailingSlash: true,
async redirects() {
return [
{ source: '/category/:name/', destination: '/blog/', permanent: true },
{ source: '/tag/:name/', destination: '/blog/', permanent: true },
{ source: '/author/:name/', destination: '/about/', permanent: true },
{ source: '/feed/', destination: '/blog/rss.xml', permanent: true },
// Old ?p=123 links
...legacyIds.map(({ id, slug }) => ({
source: '/',
has: [{ type: 'query', key: 'p', value: id }],
destination: `/${slug}/`,
permanent: true,
})),
]
},
}
Build legacy-ids.json from the wp:post_id and slug of each item in your WXR file. Next.js passes query strings through to the destination, so the new URL keeps ?p=123. Your canonical tag tells Google which version is the real one.
Rules for both
Google asks you to redirect "to the final destination directly," keep redirects for "generally at least 1 year," and not to "redirect many old URLs to one irrelevant single URL destination" (Google: site moves with URL changes). Sending tag archives to the blog index is fine, because the blog index lists the same posts. Sending every dead URL to the home page is not.
Step 5: Carry over image alt text
Google "uses alt text along with computer vision algorithms and the contents of the page" to understand images, and alt text on a linked image works as anchor text (Google: image SEO).
Alt text inside a post's HTML moves with the rich text. Images outside the body, like featured images, are easy to lose. Check them on purpose.
On Webflow:
- Add a plain text field for alt text to each Collection that has an image field, and bind the image's alt to it. Webflow's guide recommends exactly this.
- Set alt text on images in the Asset Manager. Image elements use "alt text from Asset" by default.
- Webflow's Audit panel "does not check alt text for CMS-bound images." Check those yourself. (Webflow: page-level SEO)
On Next.js, store alt text with each image in your content files and pass it to every <img> or <Image>.
To find images with missing alt text on a built page:
curl -s https://paidwick.com/blog/payroll-guide/ | grep -o '<img[^>]*>' | grep -v 'alt="[^"]'
No output means every image has alt text.
Step 6: Replace your SEO plugin's schema
Yoast outputs one JSON-LD graph per page. The base graph describes the WebPage, the WebSite, and the Organization, or a Person for a personal site. Posts add an Article, a BreadcrumbList and a Person for the author (Yoast: schema specification). If you use Rank Math or another plugin, use the samples you saved in Step 1 to see what it output.
Neither Webflow nor Next.js writes this for you. Rebuild the parts that matter:
- Organization on the home page. Google recommends it there, with
url,logoandsameAslinks to your profiles (Google: Organization structured data). - Article or BlogPosting on each post, with headline, date, author and image.
- BreadcrumbList if your pages show breadcrumbs.
On Webflow, put JSON-LD in the custom code of the Collection template page and insert CMS fields as values. Webflow says this lets you "automatically generate the markup across all the CMS item pages from that Collection" (Webflow: page-level SEO). Site-wide markup like Organization can go on the home page's custom code.
On Next.js, render the JSON-LD in the page component:
// app/[slug]/page.tsx
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
datePublished: post.date,
author: { '@type': 'Person', name: post.author },
image: post.image,
mainEntityOfPage: `https://paidwick.com/${post.slug}/`,
}
return (
<article>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<h1>{post.title}</h1>
{/* ... */}
</article>
)
Do not invent ratings or FAQ markup that is not on the page. Mark up only what readers can see.
Step 7: Sitemap, robots.txt and canonicals
On Webflow (Webflow: site-level SEO):
- The auto-generated sitemap updates every time you publish. Pages you set to noindex are left out.
- Set your default domain before launch. Otherwise the sitemap lists URLs that redirect.
- Set a global canonical URL in Site settings. Webflow then adds a self-referencing canonical to every page. Include
wwwif your default domain uses it. - Add robots.txt rules in Site settings.
On Next.js, add app/sitemap.ts (Next.js: sitemap.xml), set alternates.canonical in each page's metadata, and put robots.txt in the app folder. Our guide to moving a React app to Next.js has code for each.
On either platform, allow the AI search crawlers in robots.txt. Our robots.txt guide has a version to copy.
Step 8: Verify after launch
Run these the day you switch DNS.
Every old URL redirects or loads:
while read -r url; do
printf '%s ' "$url"
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' "$url"
done < urls.txt
Each line should be 200, or 301/308 with the right destination. Fix any 404 that had traffic.
Titles and descriptions survived. Run the Step 1 loop again into seo-after.tsv, using the new URLs, and compare the two files side by side.
The text is in the HTML. Count words on your top pages, as described in AI crawlers don't run JavaScript. Embedded widgets for pricing, reviews or FAQs often load by script and arrive empty.
Schema parses. Run your home page and a post through Google's Rich Results Test.
Search Console. Submit the new sitemap. Inspect your top 10 URLs. Watch the Page indexing report for new 404s for the next few weeks. Google says to expect "temporary fluctuation in site ranking during the move."
Checklist
| Item | Webflow | Next.js |
|---|---|---|
| URL list saved | Sitemap, analytics, Search Console | Same |
| Titles and descriptions saved | seo-before.tsv |
Same |
| Content exported | WXR to one CSV per Collection | WXR to Markdown or CMS |
| Slugs kept | Item slug = WordPress slug | Route matches old path, trailing slash kept |
| Redirects | Site settings CSV, merged not overwritten | redirects() in next.config.js |
Archives and ?p= links |
One rule each | Patterns and query matching |
| Alt text | Plain text field bound to image alt | Stored with each image |
| Schema | JSON-LD in Collection template custom code | JSON-LD in page component |
| Sitemap | Auto-generated, default domain set | app/sitemap.ts |
| Canonicals | Global canonical in Site settings | alternates.canonical per page |
| robots.txt | Site settings | app/robots.txt |
| After launch | curl loop, Rich Results Test, Search Console |
Same |
When the new site is live, our free check shows whether AI crawlers can reach it and how much text they get. If you are changing your domain at the same time, work through our domain migration checklist as a separate step.
Want this done for your site?

Every day we make one change to your website that helps AI engines name you, and a person checks each one. Email me your website and the question your buyers ask AI.