DynamicZone Stability in Strapi v5#
DynamicZones in Strapi v5's Content Manager are susceptible to a family of crashes caused by stale, orphaned, or malformed component data. These issues typically surface in three places: the data traversal layer (data.ts), the React render layer (Field.tsx / DynamicComponent.tsx), and the layout normalization layer (normalizeContentManagerLayout.ts). All known crash patterns stem from the same root shape: code that assumed a schema, a component UID, or a configuration key would always be present.
Key files#
| File | Role |
|---|---|
DynamicZone/Field.tsx | Main DZ field component — renders component list, add/remove/reorder |
DynamicZone/DynamicComponent.tsx | Individual DZ component accordion — schema lookup, drag-and-drop |
EditView/utils/data.ts | traverseData, transformDocument, removeNullValues, prepareTempKeys |
utils/layouts/normalizeContentManagerLayout.ts | Normalizes stale/invalid CM configuration before render |
hooks/useDocumentLayout.ts | formatEditLayout — builds per-document edit layout |
Crash Patterns and Fixes#
1. Detached components — traverseData crash#
Root cause. When a component is removed from a DZ via the Content-Type Builder, extractContentTypeComponents rebuilds the components dictionary from the current schema only, dropping the orphan. Any stored document data (e.g. in Content History) still references the removed component via __component. The traverseData loop would then try to access attribute.type on undefined, throwing Cannot read properties of undefined (reading 'type') and crashing the History page.
Fix (PR #26148). Added an early guard in traverseData: if !attribute, the value is passed through unchanged. The comment explains the intent directly in source:
"The schema may not contain the attribute when historical data references a component or field that no longer exists (e.g. a component detached from a dynamic zone via the content-type-builder). Pass the value through so the History view can still render the rest of the document."
Related issue: #23300
2. Null values in DZ component data#
Root cause. The API may return null for fields inside DZ components. Most inputs expect an empty string or undefined for uncontrolled/controlled state. Passing null propagates into form inputs and can crash them.
Fix. removeNullValues in transformDocument replaces null with undefined for all non-boolean attributes. This runs as part of the full transformDocument pipeline applied to every document before it enters the form. Note that boolean fields preserve null intentionally.
3. Missing schema for deeply nested relation modals#
Root cause. When a relation field is nested ≥2 levels deep through DZ components (e.g., dynamiczone → component → relation → dynamiczone → ...), formatEditLayout in useDocumentLayout.ts receives a schema that is not yet cached in useContentTypeSchema(). Accessing schema?.attributes then throws TypeError: Cannot read properties of undefined (reading 'attributes'), preventing the relation modal from rendering at all.
Fix (PR #26306). Added a guard in formatEditLayout to return a safe empty EditLayout (layout: [], components: {}, metadatas: {}, options: {}) when schema is undefined.
4. Malformed DZ entries and stale admin configuration (PR #26625)#
Root cause. Persisted or transient admin configuration can reference deleted fields, missing metadata, or DZ entries with no __component UID. Before hardening, these produced a family of Cannot read properties of undefined errors across the Content Manager:
...reading 'attributes'...reading 'list'...reading 'includes'...reading 'sort'
Fixes (merged 2026-06-16, PR #26625).
-
normalizeContentManagerLayout.ts— The newnormalizeLayoutsfunction validates every layout entry against schema attributes and drops non-renderable fields. ThehasResolvableComponentguard filters out fields whose component schema is unavailable.normalizeMetadatasdrops metadata entries for unknown fields or unresolvable components with dev-mode console warnings. -
DynamicComponent.tsx— MissingcomponentUidis now handled gracefully: the component renders withicon: nulland a localized "Unknown component" label instead of crashing. -
Field.tsx— ThedynamicComponentsByCategorymemo skips anycomponentUidwhose schema is absent from thecomponentsdictionary, preventing downstream crashes when iterating allowed components. ThehandleAddComponentcallback also guards on!schemabefore constructing the default form.
Data Flow: From API to Form#
The transformDocument pipeline is the first defensive layer; normalizeContentManagerLayout is the second (layout/config layer). Both must pass without errors for the Edit View to render safely.
Summary of Guards by Layer#
| Layer | Guard | What it prevents |
|---|---|---|
traverseData | Missing attribute → pass-through | Crash on detached/orphaned DZ component in History |
transformDocument | removeNullValues skips booleans | Null field values crashing form inputs |
prepareTempKeys | components[componentData.__component]?.attributes ?? {} | Missing component schema in DZ traversal |
normalizeContentManagerLayout | hasResolvableComponent, normalizeMetadatas | Stale layout/metadata fields with missing schemas |
formatEditLayout | Guard on undefined schema | Crash in deeply nested relation modal via DZ |
DynamicComponent | Fallback to "Unknown component" when componentUid missing | Hard crash on malformed DZ entry |
Field.tsx | handleAddComponent schema guard | Adding a component with no cached schema |