SHORT ANSWER
A redirect map in two columns, from,to, becomes a redirects array in vercel.json (up to 2,048 rules), a _redirects file on Netlify or Cloudflare Pages, or a Cloudflare Bulk Redirects list with full source urls and no header. Each conversion is one command. The trap is the status code: Netlify defaults to 301 and vercel.json to 308, both permanent, but Cloudflare Pages _redirects defaults to 302 and Vercel bulk redirects to 307, both temporary, so set the code explicitly on every row. Limits and defaults below are from each platform’s docs as read on 3 october 2026.
The file to start from
Everything below starts from a csv with the header from,to and one path pair per line. In Silentfrog that is the export option plain path pairs (wix, hubspot, other): pages that kept their url, unmatched rows and unverified needs-review rows are left out, so every line is a redirect somebody looked at (the two downloads). The plain pairs carry no wildcards; a directory rule is written out row by row, which every platform below accepts as it is.
from,to /about-us,/about /leistungen/webdesign,/services/web-design /blog/spring-launch,/news/spring-launch
The snippets split on commas. Silentfrog quotes a value only when it contains a comma, which a url path rarely does; if yours do, use a csv-aware tool instead of awk.
site settings → publishing → 301 redirects, or the data api
the two export buttons — webflow csv and generic csv — next to the option that includes needs-review matches.
The four destinations at a glance
| file | default status | wildcards | limit | |
|---|---|---|---|---|
| vercel.json redirects | vercel.json | 308 (permanent: true is the default) | /blog/:path* | 2,048 rules |
| Vercel bulk redirects | csv, json or jsonl via bulkRedirectsPath, or dashboard, api, cli | 307 (permanent: false is the default) | none | 1,000 on Pro, 10,000 on Enterprise; not on Hobby |
| Netlify _redirects | _redirects | 301 | /blog/* … :splat | none stated; wildcards advised past 10,000 |
| Cloudflare Pages _redirects | _redirects | 302 | /blog/* … :splat | 2,000 static + 100 dynamic |
| Cloudflare Bulk Redirects | csv list, no header | 301 | subpath matching per row | 10,000 Free · 25,000 Pro · 50,000 Business |
Sources: Vercel configuration redirects, Vercel bulk redirects, Netlify redirects, Cloudflare Pages redirects, Cloudflare redirect plan limits. Google treats 301 and 308 the same way and 302 and 307 the same way (Google’s redirect documentation), so 308 is fine; 302 and 307 are not what a relaunch wants.
Vercel: vercel.json or bulk redirects
Up to 2,048 rows: the redirects array. Each rule is an object with source and destination; permanent defaults to true and answers 308, and statusCode: 301 is there if you want exactly 301 (vercel.json reference). One jq call turns the csv into the array:
tail -n +2 redirects.csv | jq -R -s '
[ split("\n")[] | select(length > 0) | split(",")
| { source: .[0], destination: .[1], permanent: true } ]
' > redirects.json
# merge into an existing vercel.json
jq --slurpfile r redirects.json '.redirects = ((.redirects // []) + $r[0])' \
vercel.json > vercel.next.json && mv vercel.next.json vercel.jsonA folder that moved as a whole is one rule in path-to-regexp notation: { "source": "/blog/:path*", "destination": "/news/:path*" }. Put it after the individual rows for the same folder. On Next.js the same objects go into redirects() in next.config.js instead of vercel.json.
More than 2,048: bulk redirects. Available on Pro and Enterprise, not on Hobby, with 1,000 redirects included on Pro and 10,000 on Enterprise, and more bought in blocks of 25,000 (bulk redirects). They take a csv, json or jsonl file pointed to by bulkRedirectsPath in vercel.json, or an upload in the dashboard, api or cli, and they run before any other route. No wildcards. The csv header names the fields, and permanent defaults to false, which is a 307, so add the column:
{ echo "source,destination,permanent"
tail -n +2 redirects.csv | awk -F, '{ print $1 "," $2 ",true" }'
} > bulk-redirects.csv
# vercel.json
{ "bulkRedirectsPath": "bulk-redirects.csv" }Bulk redirects match case-insensitively by default (caseSensitive: false), and drop the query string unless preserveQueryParams is set. Vercel’s ordinary routing is case-sensitive: /About and /about are different paths, so an old site with capitals in its urls needs them in the source exactly as they were linked. Silentfrog’s export writes every path in lowercase, so add rows for mixed-case old urls by hand, from Search Console or a backlink report, or use bulk redirects.
Netlify: the _redirects file
One rule per line, old path, new path, optional status, separated by spaces. The file sits in the publish directory (public/, dist/, whatever your build writes), the default status is 301, and rules are read top to bottom with the first match winning (Netlify redirects). Writing the 301 out costs nothing and survives a later copy to a platform with a different default:
tail -n +2 redirects.csv | awk -F, '{ print $1 " " $2 " 301" }' > public/_redirectsA whole folder is a splat, /blog/* /news/:splat 301, and the asterisk may only stand at the end of a path. Netlify normalizes trailing slashes before the rules run, so /about-us also catches /about-us/ (redirect options). Two Netlify specifics matter for a relaunch:
- Shadowing. A rule does not apply when a file exists at the old path, unless it is forced with
301!. If the build still emits an old page, the redirect is silently ignored. - Size. Netlify names no hard limit, but for more than 10,000 rules it recommends wildcards or placeholders, or Edge Functions.
Rules in _redirects run before any [[redirects]] block in netlify.toml; keep the generated map in one place so the order stays readable.
Cloudflare: Pages _redirects or Bulk Redirects
A site on Cloudflare Pages reads a _redirects file in the same syntax as Netlify, with two differences that change the conversion: the default status is 302, and a file holds at most 2,000 static and 100 dynamic rules, 1,000 characters per line (Pages redirects). Same command, and the 301 is no longer optional:
tail -n +2 redirects.csv | awk -F, '{ print $1 " " $2 " 301" }' > public/_redirectsThese rules also do not apply to requests that Pages Functions serve. Past 2,100 rules, Cloudflare’s own advice is Bulk Redirects.
Bulk Redirects work for any site whose dns is proxied through Cloudflare (the orange cloud), wherever it is hosted. You upload a list, then enable it with a bulk redirect rule. The csv format has no header, and each line is source,target[,status,preserve_query_string,include_subdomains,subpath_matching,preserve_path_suffix]. The source includes the hostname, the scheme can be left out so the rule covers http and https, and the target is a full url (parameters). So the paths need a host in front:
tail -n +2 redirects.csv \
| awk -F, -v host=www.example.com '{ print host $1 ",https://" host $2 ",301" }' \
> cloudflare-bulk.csvwww.example.com/about-us,https://www.example.com/about,301 www.example.com/blog/,https://www.example.com/news/,301,,,TRUE,TRUE
The second line is the closest Bulk Redirects gets to a wildcard: subpath matching with the path suffix preserved sends /blog/post to /news/post. There are no regular expressions. Lists are capped per account: 10,000 urls across lists on Free, 25,000 on Pro, 50,000 on Business, a default of 1,000,000 on Enterprise (plan limits, as listed 3 october 2026). If the old site lived on both example.com and www.example.com, write a line for each host, or tick include subdomains on rows for the apex.
EXAMPLE
an agency moves a 1,800-page documentation site from a WordPress server to Cloudflare Pages. The map holds 1,620 redirects, inside the 2,000-rule limit, so the _redirects file works; the first deploy answers 302 everywhere, because the conversion left out the code. Adding 301 to every line and redeploying fixes it before Google has recrawled more than a handful.
Testing the result
- Deploy to a preview first. Vercel previews and Netlify deploy previews both run the redirect rules; on Cloudflare, test the list on a staging hostname before enabling it for the live one.
- Request every old path once and read status and target:
tail -n +2 redirects.csv | cut -d, -f1 | while read p; do printf '%s ' "$p"; curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' "https://PREVIEW_HOST$p" done - Every line should read
301or308. A302or307is a missing status code; a200is a rule that never matched (a shadowing file on Netlify, a case difference on Vercel); a404after the redirect is a target that does not exist. - Request a few old paths with and without a trailing slash. Netlify normalizes them; on the other platforms, check before you assume.
In short
- Start from a from,to csv of paths: Silentfrog's plain path pairs export.
- Vercel: vercel.json redirects up to 2,048 (308 by default); bulk redirects beyond that on Pro and Enterprise, 307 unless permanent is true.
- Netlify: _redirects in the publish directory, 301 by default, first match wins, files shadow rules unless forced with !.
- Cloudflare Pages: same file, 302 by default, 2,100 rules at most. Bulk Redirects: no header, hostname in the source, 301 by default.
- Write the status code on every row, then curl every old path on a preview.