Created: 12 Oct 2026, last update: 12 Oct 2026
Sitecore Content and Layout Migration Part 4: Lessons from Practice
If a standard migration approach is not enough, it is realistic to build a custom migrator that can handle the complex cases itself. With AI-assisted coding, you can move fast, especially in the early stages. Still, complex migrations take real effort. An Sitecore SXA migration that makes heavy use of rendering parameters, column structures, multilingual content and complex content structures becomes difficult quickly. Migrating 100% of the layout is often not realistic. Migrating 100% of the content usually works fine; the layout is where the real challenge sits, although getting very close to 100% automated is certainly possible with sufficient custom development.
If you can map all renderings 1-to-1, using exactly the same components and pages, you have a simpler migration than when you move to a new set of components. You may need to map one source rendering to multiple target renderings. For example: the source rendering has a fixed button, while the target rendering instead exposes a placeholder where you can place a button component. These kinds of situations are perfectly programmable with custom logic. A generic migration tool cannot do that. This is exactly the point: the migrator must not only copy content; it must understand the intent and semantics of a component.
See Sitecore Content and Layout Migration Part 2: Why Sitecore Commander Makes Your Migration Manageable for how to start building a .Net migrator
Layout: JSON or XML
In other words: do you use the Agent API and Pages API, or do you build raw layout XML and work directly with the Sitecore Authoring API? What you will quickly run into is that the JSON approach is currently too limited. Working with rendering parameters is either not possible, or it requires multiple steps. XML gives you full control. On top of that, you can write the whole layout in a single call instead of adding component by component, which gives a significant speed improvement.
For complex SXA layouts this is not a theoretical choice; it is a practical necessity. The layout is often not just a visual representation; it is a dependency model: shared versus final layout, placeholder structure, child components, device-specific overrides, column splitters, hidden states, possible soft deletes and datasource structure. If you cannot control all of that through XML, you quickly run into the limits of the API or the data model of the target.
Layout: incorrect, missing or extra content
If you look a bit deeper into what is actually stored in the layout data, you often find things that do not match what you see on the rendered page. This is exactly what causes most of the confusion in practice. For someone looking at a page less technically, it looks strange and surprising when there are extra components, or when content is missing. But behind the cause there is usually something explainable, and it is often more complex than you expected beforehand.
Sometimes you suddenly get an extra rendering in the target, or one is missing. A common cause is the soft delete. A rendering can be placed through standard values, shared layout or final layout, but a rendering that was placed once can be removed again in a later layer. In the rendering XML this is marked as a delete. In the case of the final layout, you can have a rendering that was placed in shared layout and then deleted for one specific language, while it still exists for other languages. These deletes are not rare. That means the migrator has to account for them explicitly.
The SXA GridParameters column can be set to hidden, for all devices or for some devices only. That can be a cause when you end up with extra content in the target. It is a good example of how the visual output and the technical layout data do not always tell the same story. A page can look “full” in the layout data while the rendered page looks clean, or the other way around.
Another cause of extra content can be that, in the source, a rendering was placed on an invalid, non-existing placeholder. Paying close attention during migration is important here: you do not want to silently migrate that rendering into a placeholder that does exist in the target. In practice, it is usually safer not to migrate that kind of content at all. A migration is also a good moment to clean things up: less noise and less legacy history means fewer problems later. A clean result is a fast result.
Column splitters in SXA columns can be disabled. A structure that looks like 4 columns on screen can actually have placeholder numbers like 1, 3, 4, 5. Incomplete migration code can easily produce an invalid mapping here. My preference is to migrate and map these to 1, 2, 3, 4, and simply ignore the disabled column 2. For the migration, this means the placeholder names will differ from the source, but that is something AI-assisted code generation handles very well. In practice, a small improvement in the migration structure often has more impact than trying to perfectly reproduce every legacy layout detail.
Datasources and links
An important lesson is that datasources and links are not just fields you can copy and paste; they are real architectural decisions in the content model. In practice we see three different types of datasources: local datasources that live under a page and can only be created once the page exists, shared datasources that live in the website’s data folder, and sometimes also a global shared data location for datasource items that are reused across multiple websites. These differences matter: they determine not only where you place an item, but also how carefully and how idempotently you can run the migration.
The same applies to links. Sometimes a component points to an item within the same site, sometimes to another website, and sometimes to content that has not been migrated yet.
Datasources can be local, in the data folder under the item; this is actually the easiest to migrate, although it means you can only create the datasource once the page itself exists. Or they can be shared, if they live in the website’s data folder. You may also have a global shared data location used by datasource items across multiple websites. On top of that, it is not unusual to run into datasource items that link to a different website, effectively a link you cannot create anymore with pages in SitecoreAI, and one you cannot create either if the target site has not been migrated yet. Migration to a local datasource is the way I do it, but you still need to handle this situation to avoid missing datasources in your migrated layout.
Practical rule: if a link points to another site, and that target has not been migrated yet, you cannot blindly trust the GUID. In these cases, create a minimal placeholder page in the target, a page with empty layout but the right template and correct parent structure so the link always has a valid destination. Not because it is “nicer”, but because it keeps the migration technically safe.
Deferred link resolution: reprocessing links after the first pass
An even more important practical pattern than placeholder pages alone is the way we handle links that could not be resolved the first time around. The migrator keeps track of unresolved internal links and tries to resolve them again later, once more of the site structure is available. In other words: the first pass is not always the final pass.
The idea is simple: in the first migration run we migrate as much content and as many pages as possible, we log what could not be resolved yet, and afterwards we go back to those specific cases. This works well for cross-site links and links to pages that have not been migrated yet.
In practice this usually looks like this:
First pass: migrate content and pages and log unresolved links.
Afterwards: the target structure is usually more complete.
Second pass: re-evaluate and resolve the previously unresolved links.
If needed: create placeholder pages and then reprocess the link against that new target.
This is something you genuinely need in a larger migration. If you try to resolve every link in a single pass, you end up far too dependent on the exact migration order. And that simply does not hold up in real projects. Some pages do not exist yet, some sites have not been migrated yet and you do not want a site with broken links.
In other words: migration is not a simple copy-paste operation; it is a dependency-driven workflow. You are effectively building a dependency graph: page → datasource → layout → links. If the order is wrong, you get missing references, invalid placeholders, or layout XML that is technically valid but not usable from a business point of view. This is one of the reasons a migrator is more than a parser: it has to understand architecture and dependency order.
You see this clearly during development too. While developing, we regularly switch between different modes:
Single-page mode, to test one page in isolation.
Subtree mode, to migrate a subset of the site.
Full-site mode, to run the complete website.
These modes are not only useful for performance; they are also essential for working speed. You want to be able to quickly test something on a single page, then on part of the site, and only later run the full site. This makes the migrator not just more capable, but far more usable in real day-to-day development. A practical approach is to validate on a small set of pages first, then a subtree, and only after that the full site. That is how you get fast feedback without having to rerun the entire migration every single time.
Create-or-update: why reruns matter
One of the most important lessons from migration is that a migrator should not work “one shot”; it needs to support create-or-update. It is not enough to only be able to create items. In practice you will run against the same site or the same section multiple times, a run can get interrupted, a mapping can get adjusted, or a page may already have been partially migrated before. In these situations, the migrator only becomes truly useful if it can cleanly update existing content instead of only trying to create new items. A migrator that can “update”, and can merge results again on a rerun, makes the whole workflow much easier to manage. In complex content and layout migrations, rerun-safety is not a nice-to-have; it is a requirement. And during the development phase this matters most of all: you want to be able to iterate quickly, test something new quickly, and verify changes quickly, without starting from zero every time.
AI and human interpretation
AI can help a lot here, but it is important to be clear about where its limits are. AI can recognize patterns. AI can generate mappings. AI can write large parts of the migrator. AI can detect layout variations, recognize standard patterns and reduce repetitive code. But AI cannot infer business intent without context, examples and validation.
This is exactly the difference between a custom migrator and a generic migrator. A generic migrator can automate a lot of standard behavior, but only a custom migrator can understand the real business rules behind a website or a content model. And those business rules are often not explicitly written down in a schema. They live in the way content was built up over time, in the legacy of a site, in the choice of layouts, in the semantics of placeholders, in the way content teams work, and in the context in which a page is actually used.
That is why people are still needed for:
• mapping review
• placeholder decisions
• content validation
• UX validation
• judging whether a technically correct migration is also business-correct
See Sitecore Content and Layout Migration Part 1: Layout Manager for a Marketplace app that speeds up layout validation and helps you identify issues. It can save you a lot of time.
Conclusion
Complex layout migrations can absolutely be built with AI-assisted code. It is a practical approach that can genuinely speed up a lot of the work. But it is not something you finish in a week, and certainly not if perfection is the goal. Think in terms of several days to several weeks, and sometimes longer if the site is genuinely complex. Within a few days you can have a solid basic migration running; the more complex mappings take relatively much more time. It is not easy to tell your LLM “this is the source, and this is exactly what the target should look like.” Yes, you could rebuild the target for a handful of pages and use that as input, but apart from the effort involved, hand-built layout XML is not exactly reproducible; there is real nuance there.
Complex layout migrations can certainly be built with AI, but the challenge is not translating a single page. The real difficulty is that business rules are often implicit: hidden in layout overrides, placeholder logic, datasource scope, internal site links, legacy structures and the order in which content and components logically need to exist. That logic is not always automatically discoverable, and even when you can detect it, it is not always obvious that the resulting target interpretation is actually business-correct.
That is why a migrator remains a combination of pattern recognition, explicit mapping, fallback strategies, create-or-update behavior, rerun safety, deferred resolution and human validation. Placeholder pages, reruns and idempotency, and the right order of creation are not side notes here; they are core requirements. And that is exactly the difference between a custom migrator and a generic migrator: a generic migrator can automate a lot of standard behavior, but a custom migrator can safely migrate the real content and layout of a site into the new model.
For me, that is also the best reason not to treat this kind of migration as a one-way conversion, but as an iterative process. You build the migration, you test it on a single page, you run a subtree, you adjust the mapping, you rerun it, you reprocess unresolved links, and only then do you move on to the full-site run. That is the practice. And that is exactly where a developer’s experience shows: not in building an ideal, generic model, but in being able to iterate safely and quickly over a genuinely complex real-world migration.