Key Takeaways
Somewhere in every migration kickoff, someone asks how hard this is really going to be.
The honest answer is that it depends on decisions made in the first two weeks, long before anyone touches DNS. Most migrations don't fail on launch day. They fail earlier, when the redirect map gets treated as a task for later and nobody writes the content model down.
So what does a HubSpot migration actually involve, done properly?
Five things: crawl the existing site first, build the theme in Git, script the content transport, test every redirect by machine, and treat DNS cutover as a scheduled, reversible event. Do those five and launch day is boring, which is exactly what you want. The rest of this post is the how.
Before getting into each phase, here's the shape of the whole project and where each phase tends to go wrong.
|
Phase |
What You're Doing |
Where It Goes Wrong |
|
Crawl and audit |
Spider every URL, map content types, prune dead pages |
Skipped entirely, so every later phase runs on guesswork |
|
Theme build |
HubL templates, modules, fields.json, all in Git |
Built in the design manager with no version control or rollback |
|
Content transport |
Import or script content and media into HubSpot |
Imported HTML full of inline styles and links to the old domain |
|
Redirects and cutover |
Build and test the 301 map, then flip DNS |
Untested redirects, chains, and a TTL nobody lowered in advance |
Before anyone writes a template, spider the current site. Screaming Frog or Sitebulb will do, or a headless crawler if you'd rather script it. Export every URL with its status code, canonical tag, hreflang annotations, and meta fields.
That export is your source of truth for the rest of the project.
Then cross it against analytics. A page with zero sessions in the past year is a candidate for consolidation, not migration. Every URL you decline to carry over is one fewer 301 to maintain forever, and pruning now is much cheaper than pruning after launch.
While you're in there, sort every page into a content type: landing page, blog post, resource, product page, system page. Legacy CMSs accumulate structural junk over the years. Orphaned templates, shadow taxonomies, inline styles nobody remembers adding. HubSpot's template model will force this classification on you eventually, so do it on purpose now instead of discovering it during QA.
HubSpot renders through HubL, a templating language derived from Jinja2. If you've touched Django, Twig, or Nunjucks, you already know the syntax: variable interpolation, if-blocks, filters piped with a vertical bar.
The difference is the context. HubL exposes CRM data (contact, content, request) directly in the render path, which is how the smart content and personalization features actually work.
Structure the build as a proper theme. That means a fields.json at the theme level holding brand tokens, type scales, and spacing values, so marketers can adjust settings without editing code. It means templates that map one-to-one onto the content types from your crawl. And it means modules: self-contained components with their own fields, markup, CSS, and JS, each doing one job with nothing leaking into global scope.
For cross-cutting concerns like JSON-LD, write a macro once instead of pasting script tags into every template.
The HubSpot CLI syncs your filesystem to the portal, which means the theme lives in Git, goes through pull requests, and deploys through CI like anything else you ship.
Teams that build directly in the design manager give up version control, code review, and rollback for no real reason.
Anything on the old site that looks like a table belongs in HubDB: team directories, office locations, resource libraries, comparison data.
Design the columns deliberately. Use SELECT and MULTISELECT for enumerable values instead of free text, use foreign-key rows where real relations exist, and save rich text columns for content that genuinely needs markup.
The payoff is dynamic pages. Bind one template to a table and HubSpot generates a URL per row, each with its own meta fields. Two hundred location pages collapse into one template and one table.
Query the table server side and paginate. Don't ship the whole table to the browser and filter it in JavaScript; it's slower and search engines see less of it.
HubSpot's built-in blog importer handles WordPress exports and RSS feeds reasonably well, though "reasonably" is carrying some weight in that sentence. Check the imported HTML for inline styles, absolute URLs still pointing at the old domain, and images that never made it into the file manager.
Past trivial volume, script the transport through the CMS API. Pull from the source system, transform the content (rewrite internal links, remap media to HubSpot's CDN, clean up the markup), and POST to the pages or blog endpoints. Key the script on source IDs so a re-run updates records instead of duplicating them. You will re-run it. Everyone re-runs it.
Media gets its own pipeline. Bulk-upload assets to the file manager through the API, capture the CDN URLs it returns, and use that mapping during transformation. The CDN converts to WebP and resizes on the fly through query parameters, so generate srcsets instead of uploading five sizes of every image.
Every legacy URL that changes needs a single-hop 301 to its replacement.
Build the map from the crawl, not from memory, and check it for chains and loops. A 301 that points at another 301 wastes crawl budget and weakens the signal you're trying to preserve. This is the step that decides whether your search visibility survives the move intact.
Load the result into HubSpot's URL redirect tool. For systematic path changes, one flexible pattern redirect with capture groups replaces thousands of static rows.
Then test it before cutover, not after. Script a HEAD request against every old URL and assert that each one redirects exactly once and lands on a 200. Fix every 404 the script turns up before you touch DNS.
Domain connection is CNAME based: your www host points at HubSpot's edge, and TLS provisioning follows automatically once the domain validates.
The sequencing matters more than the mechanics. Drop your DNS TTL to 300 seconds at least two days before the switch so propagation is fast and reversible. Connect and verify the domain in HubSpot while traffic still resolves to the old origin. Flip the record during a quiet window and keep the legacy environment running as a rollback target until the numbers look right.
One thing to check early: apex domains need a DNS provider that supports CNAME flattening or ALIAS records. Confirm yours does before launch day, not during it.
Not every site is ready to move, and forcing the timeline creates the exact failures described above.
If nobody can produce a content inventory, the audit comes first. If the team can't tell you which pages drive pipeline, fix measurement before moving anything.
Say your current site has thousands of thin, auto-generated pages. Migrating them as-is just relocates the problem. Prune first.
And if the redirect map can't be tested because nobody knows the full URL set, you're not ready to schedule cutover. The crawl solves that, which is why it comes first.
When the data says so, not when the new site loads.
Resubmit your XML sitemaps in Search Console and watch the coverage report for a spike in 404s or soft 404s. Confirm the new URLs are getting indexed as the recrawl wave moves through. Keep an eye on Core Web Vitals in field data; HubSpot's CDN gives you a decent baseline, but JavaScript bloat inside your own modules is still yours to own.
Expect some volatility in organic impressions for two to four weeks. That part is normal. A decline that persists past that window usually traces back to the redirect map or to content that didn't make the trip, and the crawl from step one is how you diagnose either.
Run the project this way, with the theme in version control, the content typed, the transport scripted, and the redirects tested by machine rather than by hope, and launch day should be uneventful. If the cutover itself is exciting, something went wrong earlier.