Nacelle V2 Native Migration Guide

Last updated: May 10, 2024

Overview

Detailed in this document will be step-by-step guide illustrating the migration effort needed to natively migrate from Nacelle V1 to V2. There will be examples and patterns identified to help make the developer experience as seamless as possible for this big jump to our better version!

Prerequisites

The following prerequisites must be completed before following this guide:

  1. Account created on V2, data connectors configured for V2 space, and products, collections, and content are indexed properly

    1. Account setup

    2. Connector setup

  2. Checkout is no longer proxied through Nacelle. This means that if a Merchant is dependent on Recharge through Nacelle, there will be additional scope that is required to account for the development of Merchant’s own Recharge checkout capabilities

Instantiate @nacelle/storefront-sdk

One can find the official documentation of the @nacelle/storefront-sdk :

  1. Nacelle

  2. NPM

Start by identifying where the previous @nacelle/client-js-sdk exists and follow the instructions linked above to replace the V1 SDK.

GQL Queries

With @nacelle/storefront-sdk the capabilities to run your own custom GQL queries are now easier since it comes packaged with a new query method which takes in a GQL query and variables. There is also a documentation playground for our new V2 GQL Schema on Nacelle Docs

If a merchant had custom V1 GQL queries. They will need to be refactored to use the new V2 GQL queries names and new parameters. The table below describes the most commonly used V1 queries and their V2 equivalence.

V1 Query Name

V2 Query Name

getProductByHandle

getProducts

allProducts

getContentByHandle

getContent

allContent

getCollectionByHandle

getCollection

allProductCollections

This is a great time to consider refactoring SDK calls to leverage GQL custom queries (especially for Content). Once typed fields is released on the Product Roadmap, merchants will only need to do a light refactor to their GQL queries to take advantage of this new capabilities.

SDK Function Name Changes

Below is a table that describes the most common SDK function names in V1 and their respective counterpart in V2.

Linked here are the official documentation for:

  1. [V1] @nacelle/client-js-sdk

  2. [V2] @nacelle/storefront-sdk

Please reach out if there is confusion on a V1 SDK Function that doesn’t exist in V2.

Once you identify from the table or the documentation of the new function name, make the necessary update.

 

Please note that the functions in the table can take additional parameters. The table’s purpose is just to highlight most common. Please view the documentation on the respective SDK for all available function parameters.

 

Objective

V1

V2

Comments

Get all products

v1.data.allProducts()

v2.products({ maxReturnedEntries: -1 });

 

Get all collections

v1.data.allCollections()

v2.productCollections({ maxReturnedEntries: -1 });

 

Get all content

v1.data.allContent()

v2.content({ maxReturnedEntries: -1 });

 

Get Collection by Handle

v1.data.collection({ handle: 'collection' })

v2.productCollections({ handles: ['collection-1'] });

 

Get Product by Handle (s)

  1. v1.data.product({ handle: 'product' })

  2. v1.data.products({ handles: ['handle1','handle2'] })

v2.products({ handles: ["product-1", "product-2"] });

V2 products handles both cases of singular and multiple handles. If it is a single handle, just pass the single handle into the handles array parameter

 

V2 response will be an Array

Get Content by Handle

v1.data.content({ handle: 'content1' })

 

v2.content({ handles: ['content1'] });

V2 response will be an Array

Page(s)

  1. v1.data.page({ handle: 'page1' })

  2. v1.data.pages({ handles: ['page1', 'page2'] })

 

  1. v2.content({ handles: ['page1'], type: 'page' });

  2. v2.content({ handles: ['page1', 'page2'], type: 'page' });

 

Articles

  1. v1.data.article({ handle: 'article1'})

  2. v1.data.articles({ handles: ['article1', 'article2'] })

  1. v2.content({ handles: ['article1'], type: 'article' });

  2. v2.content({ handles: ['article1', 'article2'], type: 'article' });

V2 does not have the concept of blogHandle as a param. Therefore, filtering the initial results by field is required.

i.e - Using a filter

 

1articles = articles 2 .filter((article) => article.fields?.blogHandle === 'blogHandle');

Blogs

v1.data.blog({ handle: 'blog1' })

v2.content({ handles: ['blog1'], type: 'blog' });

V2 response will be an Array

Blog Pages

client.data.blogPage({ handle: 'blog', paginate: true, itemsPerPage: 6 })

 

v2.content({ handles: ['blog1'], type: 'blog' });

Once the blog is found, iterate over the field name: articleList . This is assumed to exist in the data model since the V1 function was being used.

 

V2 response will be an Array

Data Shape Refactor

V2 Response schema has changed. This is due to the updated schema introduced in V2 for scalability and better internationalization support. This requires refactoring all pages, dependent components, and client-side calls to Nacelle to properly access the newly expected data.

 

If there are any client-side calls, this is a perfect time to ask yourself, “Are these client-side calls necessary?” Can they be moved to be statically generated?

When troubleshooting this refactor and you are uncertain of why an object is referencing a null or undefined there are 2 ways that is recommended to help:

  1. Viewing the JSON directly in the V2 Dashboard

  2. Good ol' console.log the object out

Products

Updated Fields

The following product fields location have been changed. If you are using these fields, they will need to be updated:

  1. product.handle

  2. product.title

  3. product.description

  4. product.featuredMedia

  5. product.options

They can now be found under product.content.fieldName

i.e product.handle → product.content.handle

Removed Fields

These product fields NO LONGER EXIST and will need to be calculated if still required:

  1. product.priceRange (Example)

    1.  

      1const formatPrice = (price) => { 2 if (Number.isInteger(price)) return price.toFixed(1); 3 return price.toString(); 4}; 56const variantPrices = 7product.variants 8 ?.map((variant) => parseFloat(variant.price)) 9 .sort((a, b) => a - b) 10 .map((price) => formatPrice(price)); 1112const priceRange = { 13 min: variantPrices[0], 14 max: variantPrices[variantPrices.length - 1], 15 currencyCode: priceCurrency 16}

Variants

Updated Fields

The following variant fields location have been changed. If you are using these fields, they will need to be updated:

  1. product.variants[].selectedOptions

  2. product.variants[].published

  3. product.variants[].featuredMedia

  4. product.variants[].title

They can now be found under product.variants[].content.fieldName

i.e product.variants[].selectedOptions → product.variants[].content.selectedOptions

Collection

Updated Fields

The following collection fields location have been changed. If you are using these fields, they will need to be updated:

  1. collection.handle

  2. collection.title

  3. collection.description

  4. collection.metafields

  5. collection.featuredMedia

They can now be found under collection.content.fieldName

i.e collection.handle → collection.content.handle

Removed Fields

These collection fields NO LONGER EXIST:

  1. collection.productLists

    1. Has been replaced by collection.productConnections.edges[]. This will return all the product data per collection so subsequent API requests are no longer needed.

Content

Content has gotten the biggest rework moving to V2. This work was necessary to better handle all different types of Content Models and future state of GQL within Nacelle.

Contentful

The biggest thing to remember here is the introduction of the fields property to the top level.

Example Content Model:

Homepage

→ Title

→ Description

→ Sections[]

In V1, you would access these such as: content.title, content.description, content.sections

In V2, it is now: content.fields.title, content.fields.description, content.fields.sections

Let’s take it a step further:

Homepage

→ Title

→ Description

→ Sections[]

→ HeroBanner

→ FeaturedMedia

→ Image

In V1, you would access Image Url: homepage.sections[0].fields.featuredMedia.fields.file.url

In V2, it is now: homepage.fields.sections[0].fields.featuredMedia.fields.file.url

Space

In V1, there was a concept of Linked Lists and Metafields . They were retrieved by the SDK using v1.data.space() . This concept has changed in V2 and is now split up.

  • To Access Linked Lists , now called Navigation in V2: Link

    • v2.navigation()

  • To Access Metafields, now called Space Properties in V2: Link

    • v2.spaceProperties()