Saltar al contenido principal

Migrating Your App to NubeSDK

This guide shows you how to convert a classic Tiendanube/Nuvemshop script — one that uses document, window, jQuery, or direct DOM manipulation — into a NubeSDK app that runs inside a secure Web Worker.

Each section presents a before (legacy) and after (NubeSDK) comparison so you can map your existing code to the new model.


1. Entry point

Every NubeSDK app exports a single App function that receives the nube instance. This replaces your legacy IIFE, DOMContentLoaded, or global script.

BEFORE
// Legacy: script runs directly in the page
(function () {
document.addEventListener("DOMContentLoaded", () => {
// your app logic
});
})();
AFTER
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
// your app logic — executed automatically by the SDK
}
consejo

Use npm create nube-app@latest to scaffold a project that is already configured with the right entry point, build tool, and types. See Getting Started for details.


2. Reacting to page or route changes

Legacy scripts typically check window.location or listen to popstate. In NubeSDK you listen to events that tell you exactly which page the user is on and what data is available.

BEFORE
// Legacy: check URL to know which page you're on
if (window.location.pathname.includes("/product/")) {
// product page logic
}
AFTER (STOREFRONT)
nube.on("location:updated", ({ location }) => {
if (location.page?.type === "product") {
const product = location.page.data.product;
console.log("Product page:", product.name);
}
});

For checkout apps, listen to checkout:ready instead:

nube.on("checkout:ready", ({ location }) => {
const { page } = location;

if (page.type === "checkout" && page.data.step === "start") {
// Checkout start page — contact form, address, shipping
}

if (page.type === "checkout" && page.data.step === "payment") {
// Payment step
}
});

Check the available events in the events documentation .


3. Listening to user interactions

In legacy scripts you'd query the DOM for elements and attach event listeners. NubeSDK provides store events instead — you react to data changes, not clicks on particular elements.

BEFORE
// Legacy: click handler on a DOM element
document.querySelector(".buy-button").addEventListener("click", () => {
// logic when buy button is clicked
});
AFTER
// NubeSDK: react when the cart changes
nube.on("cart:update", ({ cart }) => {
console.log("Cart now has", cart.items.length, "items");
console.log("Total:", cart.prices.total);
});
información

There is no 1:1 equivalent of DOM click handlers. Instead, map your logic to the data change that the click produces. A click on "Buy" produces a cart:update; a click on "Select shipping" produces a shipping:update, etc.


4. Injecting UI into the page

Instead of creating elements with document.createElement or injecting HTML with innerHTML, you render components into slots.

BEFORE
// Legacy: inject a banner after the line items
const banner = document.createElement("div");
banner.innerHTML = '<p style="padding:16px">Thank you!</p>';
document.querySelector(".line-items")?.after(banner);
AFTER (JSX)
import { Box, Text } from "@tiendanube/nube-sdk-jsx";

nube.render("after_line_items", () => (
<Box padding="16px">
<Text>Thank you for your purchase!</Text>
</Box>
));

You can also use the declarative API (no JSX required):

import { box, txt } from "@tiendanube/nube-sdk-ui";

nube.render("after_line_items", () =>
box({
padding: "16px",
children: [txt({ children: "Thank you for your purchase!" })],
}),
);
consejo

Don't know which slot to use? Check Checkout Slots and Storefront Slots for a full visual reference.


5. Rendering dynamic UI based on state

In legacy apps you'd read DOM values or make API calls and then update elements. In NubeSDK, nube.render() can receive a function — the SDK calls it with the current state every time it changes.

BEFORE
// Legacy: read cart total from DOM and show a message
const totalEl = document.querySelector(".cart-total");
const total = parseFloat(totalEl?.textContent?.replace("$", "") ?? "0");

if (total > 100) {
const msg = document.createElement("p");
msg.textContent = "You qualify for free shipping!";
totalEl?.after(msg);
}
AFTER
import { Text } from "@tiendanube/nube-sdk-jsx";

nube.render("after_line_items", (state) => {
if (state.cart.prices.total > 100) {
return <Text>You qualify for free shipping!</Text>;
}
return null;
});

6. Styling

Legacy apps often inject <style> tags or add inline styles. NubeSDK provides styled(), StyleSheet.create(), and a theme system that adapts to each store.

BEFORE
// Legacy: inject custom styles into the page
const style = document.createElement("style");
style.textContent = `
.my-badge {
background: #e91e63;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
`;
document.head.appendChild(style);

const badge = document.createElement("span");
badge.className = "my-badge";
badge.textContent = "NEW";
document.querySelector(".product-title")?.prepend(badge);
AFTER
import { Text } from "@tiendanube/nube-sdk-jsx";
import { styled, theme } from "@tiendanube/nube-sdk-ui";

const Badge = styled(Text)`
background: ${theme.color.accent};
color: white;
padding: 4px 8px;
border-radius: ${theme.border.radius};
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
`;

nube.render("before_product_detail_name", () => <Badge>NEW</Badge>);
consejo

Use theme tokens instead of hard-coded colours — your component will automatically match the store's theme. See Styling for the complete token list.


7. Reading and reacting to cart data

BEFORE
// Legacy: scrape the cart total from the page
const priceText = document.querySelector(".total-price")?.textContent;
const total = parseFloat(priceText?.replace(/[^0-9.]/g, "") ?? "0");
console.log("Cart total:", total);
AFTER
// NubeSDK: cart data is available in the state
const { cart } = nube.getState();
console.log("Cart total:", cart.prices.total);
console.log("Items:", cart.items.length);
console.log("Subtotal:", cart.prices.subtotal);
console.log("Shipping:", cart.prices.shipping);

Or react to changes over time:

nube.on("cart:update", ({ cart }) => {
console.log("Cart changed — new total:", cart.prices.total);
});

8. Validating the cart / blocking checkout

Legacy scripts would prevent form submission or add alerts. NubeSDK uses a formal config + validate flow.

BEFORE
// Legacy: block the checkout form from submitting
document.querySelector("#checkout-form")
?.addEventListener("submit", (e) => {
const items = document.querySelectorAll(".cart-item").length;
if (items < 3) {
e.preventDefault();
alert("You need at least 3 items to continue");
}
});
AFTER
// Step 1: Tell NubeSDK your app validates the cart
nube.send("config:set", () => ({
config: { has_cart_validation: true },
}));

// Step 2: React to cart changes and send validation result
nube.on("cart:update", ({ cart }) => {
const status = cart.items.length < 3 ? "fail" : "success";
const reason = status === "fail"
? "You need at least 3 items to continue"
: undefined;
nube.send("cart:validate", () => ({
cart: { validation: { status, reason } },
}));
});

9. Making HTTP requests

Legacy scripts use XMLHttpRequest or fetch on the main thread. The Worker environment supports fetch natively.

BEFORE
// Legacy: fetch data and inject into the page
fetch("https://api.example.com/promotions")
.then((res) => res.json())
.then((data) => {
const el = document.createElement("div");
el.textContent = data.message;
document.body.appendChild(el);
});
AFTER
import { Text } from "@tiendanube/nube-sdk-jsx";

export function App(nube: NubeSDK) {
fetch("https://api.example.com/promotions")
.then((res) => res.json())
.then((data) => {
nube.render("before_main_content", () => (
<Text>{data.message}</Text>
));
});
}

10. Using localStorage / sessionStorage

Web Workers don't have direct access to localStorage. NubeSDK provides async equivalents.

BEFORE
// Legacy: store a flag in localStorage
localStorage.setItem("promo_seen", "true");
const seen = localStorage.getItem("promo_seen");
AFTER
const browser = nube.getBrowserAPIs();

// Store a value (returns a Promise)
await browser.asyncLocalStorage.setItem("promo_seen", "true");

// Retrieve it
const seen = await browser.asyncLocalStorage.getItem("promo_seen");

// Store a value that expires in 60 seconds
await browser.asyncLocalStorage.setItem("promo_seen", "true", 60);

11. Navigating programmatically

BEFORE
// Legacy: redirect to a different page
window.location.href = "/products";
AFTER
const browser = nube.getBrowserAPIs();
browser.navigate("/products");
peligro

navigate only works within the current domain. External URLs are not supported.


12. Reacting to shipping and payment changes

BEFORE
// Legacy: detect shipping option changes
document.querySelector("#shipping-options")
?.addEventListener("change", (e) => {
console.log("Shipping changed to:", e.target.value);
});
AFTER
nube.on("shipping:update", ({ shipping }) => {
if (shipping?.selected) {
console.log("Shipping method changed to:", shipping.selected);
}
});

nube.on("payment:update", ({ payment }) => {
console.log("Payment method changed to:", payment?.selected?.name);
});

13. Reacting to customer data

BEFORE
// Legacy: read customer email from a form field
const email = document.querySelector("#contact-email")?.value;
console.log("Customer email:", email);
AFTER
nube.on("customer:update", ({ customer }) => {
console.log("Customer email:", customer?.contact?.email);
console.log("Shipping address:", customer?.shipping_address);
});

14. Adding items to the cart

BEFORE
// Legacy: submit a form to add to cart
Tiendanube.addToCart({ variant_id: 123, quantity: 1 });
AFTER
nube.send("cart:add", () => ({
cart: {
items: [{ variant_id: 123, product_id: 321, quantity: 1 }],
},
}));

nube.on("cart:add:success", ({ cart }) => {
console.log("Item added!");
});

nube.on("cart:add:fail", ({ cart }) => {
console.log("Failed to add item");
});

15. Order tracking (checkout success page)

BEFORE
// Legacy: scrape order data from the success page
const orderId = document.querySelector(".order-id")?.textContent;
analytics.track("purchase", { orderId });
AFTER
nube.on("order:update", ({ order }) => {
console.log("Order completed:", order.id);
// Send to analytics, fire conversion pixels, etc.
});

What you can't do in NubeSDK

Because your code runs in a Web Worker, these APIs are not available:

Legacy APIWhy it's not availableAlternative
document / windowNo DOM in workersUse nube.render() and events
localStorage / sessionStorageMain-thread APInube.getBrowserAPIs().asyncLocalStorage
window.location.href = ...Main-thread APInube.getBrowserAPIs().navigate(...)
React, Vue, AngularNo DOM renderingUse NubeSDK components (JSX or declarative)
jQueryNo DOMUse events and nube.render()
XMLHttpRequestAvailable but preferfetch() (works in workers)

Migration checklist

Use this list to track your progress:

  • Created a project with npm create nube-app@latest
  • Main entry file exports App(nube: NubeSDK)
  • All UI is rendered through nube.render() — no document.* or innerHTML
  • All logic uses nube.on, nube.send, nube.getState
  • No references to window, document, jQuery, React, or other DOM libraries
  • localStorage replaced with asyncLocalStorage / asyncSessionStorage
  • Styling uses styled(), StyleSheet.create(), or theme tokens — no <style> injection
  • Tested in dev mode (npm run dev) with the script pointing to localhost:8080/main.min.js
  • "Uses NubeSDK" flag enabled in the Partner Portal
  • App works on Patagonia theme (storefront) or any theme (checkout)

Useful resources

Need help? Contact api@tiendanube.com / api@nuvemshop.com.br . Missing a component, slot, or event? Email with details and visual references.