Migrate from Studio SDK v1 to SDK v2
Overview
At the heart of every website built with Contentful Studio is the Studio SDK. Over the last two years we’ve been making constant improvements to the SDK by adding some incremental features and fixing bugs.
During that time, we have also accumulated a list of improvements and fixes to the SDK which cannot be introduced in an incremental fashion and we need to bump a major version of the SDK to signal some breaking changes. However, in 98% of cases the upgrade from v1 to v2 should be as simple as a dependency bump.
Code and logic adjustments will be necessary only in the case when your website was relying on some specific niche features (eg. like using Link or Array variable bindings), or relied on some undocumented utility functions or logic.
This guide outlines the list of changes to help you understand the level of changes and whether any code and logic adjustments are required.
What changed?
If your Studio powered website is using Array/Link variable bindings you have to adjust the code according to the instructions in this guide.
Handling of Array/Link variable bindings
The most significant breaking change in the SDK v2 concerns the mechanism of how Array or Link variable bindings are passed into your React component props.
Let’s look at how receiving props when using Link variable binding used to work in SDK v1. Usually, your React component would have the following structure:
Note that in this example the React component has immediate access to:
- all immediate (flat) fields of the
myBlogPostentrymyBlogPost.fields.titleTextmyBlogPost.fields.introText
- the Asset present on the entry
myBlogPost.fields.imageAsset (to get actual url of the asset you’d need to go deepermyBlogPost.fields.image.fields.file.url)
- the referenced entry (aka nested entry) (and all of its primitive and asset fields)
myBlogPost.fields.authormyBlogPost.fields.author.fields.fullNamemyBlogPost.fields.author.fields.authorImage.fields.file.url(nested asset url)
Whilst availability of the immediate fields of the actual myBlogPost entry are expected to be there, the actual values of the referenced entries (eg. myBlogPost.fields.author) are “downloaded” and inserted instead of automatically linked. This happens because, behind the scenes, SDK v1 stitches together the tree of data. The “stitching” mechanism, takes the “head” entry myBlogPost and recursively follows its references (asset references like myBlogPost.fields.image or entry references like myBlogPost.fields.author ) and stitches together a tree of data.
The mechanism for this automatic data stitching provided some convenience in SDK v1, but it also introduced some disadvantages:
- The stitching mechanism was not rigorously defined, could have had inconsistent tree depth, mutated some entries in the store.
- It didn’t provide enough control over the stitching mechanism. For example, if your React component only needs 1 level of the entry, this stitching may not be required at all. Meanwhile, if your React component needs 3 levels of nesting, the stitching mechanism may not provide enough.
Also, there was a side effect: during the stitching, when receiving an entry into a component, it would mutate it from its original “unstitched” state which could cause some unexpected results in other components that were reusing the same entry.
To solve all of these issues, in SDK v2 we have introduced a stricter and more consistent mechanism for your React component props to receive bindings to entries when using Link/Array variable bindings. The binding mechanism now relies on these clear principles:
- All entries you receive are “shallow” (their reference fields are just links)
- All entries you receive are immutable (processed with
Object.freeze()to ensure consistency) - The stitching mechanism of your data tree is now a transparent mechanism that you can control and explicitly have to add to your components.
To migrate your website from SDK v1 to SDK v2, the most significant change would be changing and adding mechanism for “stitching” your data tree. We’ll provide more details on migration below. Meanwhile let’s outline some less impactful changes of the SDK v2.
Utility methods to handle resolving Array link variable binding
As the mechanism for “stitching” data for Array/Link variable bindings is now moved out the SDK and into React components, we provided a few methods to control and implement this mechanism.
One of the foundational steps for stitching data would be taking the reference and resolving it into the actual entry. To accomplish this in your React component, the following pattern can be used:
Note that the approach shown above relies on the resolveLinkToEntryInSomeWay() method.
In SDK v2 we provide such a method, as part of the new utility object that we expose, called inMemoryEntities. This utility object represents the concept of the “in memory entity store”. That store is preloaded by the SDK and contains all of the entities and assets referenced in the experience. Your React components have access to this store via the inMemoryEntities namespace or useInMemoryEntities.
Utility methods to load additional entities into memory
When you load a webpage powered by the Studio SDK v2, it will load all the entities bound in that webpage. And it will also recursively download up to 3 levels of referenced entries (4 levels of assets).
This recursive downloading ensures that when your React component receives a prop, myBlogPost, enough of its references are already downloaded into the SDK memory and can be accessed by the React component, using inMemoryEntities.maybeResolveLink().
However, sometimes there may use cases, where your React components rely on even
deeper tree of references available from the entry passed in via Link or Array.
The diagram below shows that in case your component wants to have access to deeply nested field like:
You will not get such a deep entity automatically loaded by the SDK, but you
will need to download it yourself. There’s a detailed guide on how to
Manually download entries and assets into the SDK memory
and how to use inMemoryEntities.addEntities() method.
However, manually loading such a deep data would be considered an anti-pattern and we’d advise to use it as a last resort.
Universal utility methods
Many methods are implemented as TypeScript typeguards which will help with typing.
- The method
isLinkToEntry()is added as the missing counterpart of the already existingisLinkToAsset().
There is also a minor improvement for the logic of isLink(): detection was made stricter. Now, it also expects target.sys.linkType defined (not just the presence of the target.sys.type === 'Link' ).
Fixed semantics of useFetchBySlug() return values
When using Contentful Studio you can publish a website which is pure React or NextJS with Server-Side Rendering (SSR). To publish production sites, most of our customers chose NextJS which conveniently allows rendering the initial version of the Studio website using SSR.
For the Studio SDK to render the website, it must first load the webpage data, meaning loading the experience. The SDK (since v1) provided two mechanisms to do that:
useFetchBySlug()hook for pure React CSR sitesfetchBySlug()method for NextJS SSR sites
In this SDK v2 we fixed small inconsistency in the return value of the useFetchBySlug() which affected only pure React SSR sites.
Previously, in the SDK v1:
In the SDK v2 we fixed the semantics of the experience variable:
- it will start as
undefined - and it will ONLY become defined when an experience is truly loaded when the webpage runs in “standalone” (outside of Studio editor iframe)
- the variable will still stay
undefinedin case the webpage runs inside of Studio editor iframe- PRO TIP: This is why in your Page components in pure React sites, you can use it to detect standalone mode e.g.
if (experience) { do something only in standalone mode}
- PRO TIP: This is why in your Page components in pure React sites, you can use it to detect standalone mode e.g.
Let’s look at an example of how semantics of experience were fixed in SDK v2
Changes details for handling Array/Link variable bindings
- All entries you receive are “shallow”
In the SDK v2, when you receive a React prop (configured with Link variable binding) named myBlogPost the prop will have one of the two values:
-
undefinedin case user who uses Studio didn’t bind any data (or bound to an empty link, to a link to deleted item or to a link to archived item). -
“shallow” shape of the
MyBlogPostentry (with unresolved links), e.g.:
Note that every reference field of the myBlogPost entry (including asset reference, as assets are internally represented as references), is just a link. It’s not a real entry.
This is the main difference vs SDK v1, where every reference field of the entry would have actual resolved entry, similar to this shape:
In some simple cases your React component may be satisfied with just “shallow” entry, but in most of the cases you will want to have entire data tree stitched. In the SDK v2 we provide explicit tools - utility functions - which can help with that. We’ll describe the dedicated section further below.
- All entries your React component receives are immutable
In the SDK v2 when your React component receives a prop e.g. myBlogPost that has binding to Link variable type, the object you receive will be immutable. The entry is processed recursively with Object.freeze.
Thus, if you’re planning to mutate this object, you have to clone it first.
Example of using the value of the prop as normal for read-only use-cases:
Example for use cases where you plan to mutate the entry
- Mechanism to “stitching” your data tree must be explicitly added to your components
When migrating from Contentful Studio SDK v1 to SDK v2, you have to alter the code of your React component to manually resolve field references for all the entries.
For example, when you’re creating a custom React component that uses binding, that binding will fall into one of the two categories:
- binding to scalar variables
- for example.
Text(prop passed to component isstring)
- for example.
- binding to objects (entries with references)
Link(prop passed to component is a JS object representing entry)Array(prop passed to component is a JS Array holding entries, each of them is a JS-object representing an entry)
In case your React components used Link or Array variable bindings, then you’d need to add logic which “stitches” the data tree aka “resolve references”.
You can resolve single level of references explicitly, or you can do it recursively.
The principles of the changes are shown in the snippet below:
Migration checklist
Here’s a quick checklist for migration:
- if you’re using
ArrayorLinkvariable bindings you have to add to the component logic to resolve references or stitch the data tree that starts from the entity passed viaLinkbinding (or an array of entities passed viaArraybinding). - if you’re using
useFetchBySlug()(which is rarely used in NextJS apps over preferredfetchBySlug()) you have to check that you’re not relying onexperiencebeing true all the time. And that your handling ofuseFetchBySlug()return values corresponds to the logic described earlier in this guide.