<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lucy </title>
    <description>The latest articles on DEV Community by Lucy  (@lucy1).</description>
    <link>https://dev.to/lucy1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1790752%2F3de53444-41e1-423d-843a-7e3727c1f878.png</url>
      <title>DEV Community: Lucy </title>
      <link>https://dev.to/lucy1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lucy1"/>
    <language>en</language>
    <item>
      <title>Shopify AI Search Optimization: How to Make Your Store Visible Inside ChatGPT, Perplexity, and Google AI Mode</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Wed, 29 Jul 2026 06:22:47 +0000</pubDate>
      <link>https://dev.to/lucy1/shopify-ai-search-optimization-how-to-make-your-store-visible-inside-chatgpt-perplexity-and-4d45</link>
      <guid>https://dev.to/lucy1/shopify-ai-search-optimization-how-to-make-your-store-visible-inside-chatgpt-perplexity-and-4d45</guid>
      <description>&lt;p&gt;&lt;em&gt;Direct answer: Shopify AI search optimization (GEO) means structuring your product and content pages so ChatGPT, Perplexity, Google AI Mode, and Copilot can read, trust, and cite them. In practice that comes down to three things: clean schema.org markup on every product page, a healthy Google/Bing Merchant Center feed, and content written in a self-contained, fact-dense style that AI systems can lift and quote. Below is exactly how the major platforms pull Shopify product data in 2026, and a checklist you can run today.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Shopify Merchants Can't Ignore AI Search Anymore
&lt;/h2&gt;

&lt;p&gt;For most of the last decade, "getting found" meant ranking in ten blue links. That's no longer the whole game. Shoppers now ask ChatGPT to compare running shoes, ask Perplexity what the best gift is for a new homeowner, and ask Google AI Mode to shortlist skincare brands before they ever open a browser tab to search normally.&lt;/p&gt;

&lt;p&gt;The interesting part is that this isn't a hypothetical shift for Shopify merchants specifically. Shopify has already built the plumbing for it. Shopify Catalog automatically syndicates product data to connected AI platforms including Perplexity, ChatGPT, and Google AI Mode, and when a shopper completes a purchase through one of these assistants, checkout still happens on the merchant's own store using their existing payment setup, according to &lt;a href="https://www.shopify.com/blog/perplexity-shopping" rel="noopener noreferrer"&gt;Shopify's own guide to Perplexity Shopping&lt;/a&gt;. In other words, the infrastructure exists. Most stores just aren't feeding it correctly.&lt;/p&gt;

&lt;p&gt;That's the gap this post covers: what "AI search optimization" (often called Generative Engine Optimization, or GEO) actually means for a Shopify store, how each AI platform sources its data, and what a merchant or their dev team should fix first.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do AI Shopping Assistants Actually Find Your Products?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi1qn3ozabxxflauuap6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi1qn3ozabxxflauuap6v.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
This is the part most merchants get wrong, because it's easy to assume ChatGPT "crawls your site" the way Googlebot does. It mostly doesn't. Each platform has a different pipeline, and knowing which one matters for you changes where you spend effort.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ChatGPT Shopping&lt;/strong&gt; draws roughly 83% of its product recommendations from Google Shopping's organic index, based on a March 2026 analysis of 43,000 carousel products that found base64-encoded Google Shopping parameters embedded in ChatGPT's source code, confirming a direct pipeline from Google Merchant Center feeds into ChatGPT's results, according to &lt;a href="https://www.getpassionfruit.com/blog/how-to-optimize-product-feeds-for-chatgpt-shopping-perplexity-and-ai-commerce" rel="noopener noreferrer"&gt;a feed-optimization breakdown of that research&lt;/a&gt;. Shopify merchants in the US get automatic catalog syndication into ChatGPT through Shopify's own Agentic Storefront integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perplexity Shopping&lt;/strong&gt; is closer to a real-time researcher. It crawls your live HTML and cites the exact page it pulled facts from, and its shoppers convert at a notably higher average order value than other AI referral traffic, per Shopify's Perplexity guide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google AI Mode&lt;/strong&gt; leans on the existing Shopping Graph, meaning your Google Merchant Center feed and on-page schema still do most of the work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft Copilot&lt;/strong&gt; runs on Bing Merchant Center and, as of a January 2026 rollout, offers checkout inside the chat window itself for eligible Shopify merchants, per &lt;a href="https://aiadvantageagency.com/ai-shopping-platforms-for-ecommerce/" rel="noopener noreferrer"&gt;an ecommerce AI platform comparison&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Primary data source&lt;/th&gt;
&lt;th&gt;What actually moves the needle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ChatGPT Shopping&lt;/td&gt;
&lt;td&gt;Google Shopping feed (via Shopify Agentic Storefront)&lt;/td&gt;
&lt;td&gt;Clean Merchant Center feed, GTIN, full descriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perplexity Shopping&lt;/td&gt;
&lt;td&gt;Live HTML crawl + Shopify Catalog&lt;/td&gt;
&lt;td&gt;On-page schema, real prices, return policy fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google AI Mode&lt;/td&gt;
&lt;td&gt;Shopping Graph&lt;/td&gt;
&lt;td&gt;Merchant Center feed + Product/Offer schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft Copilot&lt;/td&gt;
&lt;td&gt;Bing Merchant Center&lt;/td&gt;
&lt;td&gt;Feed hygiene + Copilot Checkout enrollment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The practical takeaway: your Google Merchant Center feed and your on-page schema aren't separate SEO and "AI SEO" tasks anymore. They're the same infrastructure two different systems are reading.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Structured Data Does an AI Engine Actually Need From a Product Page?
&lt;/h2&gt;

&lt;p&gt;Structured data is how you tell a machine that "$89" is a price and "4.7/5" is a rating, rather than making it guess from surrounding text. Most Shopify themes add basic Product schema automatically, but an audit of 2,400 Shopify product pages found only 9% carried the structured data required for ChatGPT or Perplexity to reliably recommend them, per &lt;a href="https://wrkngdigital.com/post/how-shopify-stores-get-recommended-by-chatgpt-and-perplexity-in-2026" rel="noopener noreferrer"&gt;an analysis of Shopify AI visibility&lt;/a&gt;. The gap is usually in the fields nobody thinks about: shipping details, return policy, and review counts.&lt;/p&gt;

&lt;p&gt;A reasonably complete Product schema block looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example Product Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.shopify.com/example.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A full, specific product description, not a five-word title restated."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Brand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Brand"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SKU-1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gtin13"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0012345678905"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"offers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Offer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"priceCurrency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"89.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"availability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org/InStock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"shippingDetails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OfferShippingDetails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"shippingRate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MonetaryAmount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hasMerchantReturnPolicy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MerchantReturnPolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"returnPolicyCategory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org/MerchantReturnFiniteReturnWindow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"merchantReturnDays"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggregateRating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AggregateRating"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ratingValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reviewCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"182"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two fields worth flagging specifically because they're the ones most themes skip: &lt;code&gt;hasMerchantReturnPolicy&lt;/code&gt; and &lt;code&gt;shippingDetails&lt;/code&gt;. Shipping and return terms are now a comparison criterion AI assistants use to decide between two similar products, not just legal boilerplate buried in a footer link.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4zoxdvrsx757nqta7mcg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4zoxdvrsx757nqta7mcg.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Does llms.txt Actually Help a Shopify Store Get Cited?
&lt;/h2&gt;

&lt;p&gt;This one deserves an honest answer rather than a hype-cycle one. llms.txt is a proposed standard, introduced in September 2024 by Jeremy Howard of Answer.AI, that puts a curated Markdown map of a site's key pages at the domain root, similar in spirit to robots.txt but aimed at language models instead of search crawlers, as documented in &lt;a href="https://searchengineland.com/llms-txt-proposed-standard-453676" rel="noopener noreferrer"&gt;Search Engine Land's coverage of the proposal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It has real traction in one specific use case: developer documentation for coding assistants and AI agents. It is far shakier as a general AI-search ranking lever. One large-scale analysis of AI bot traffic found that requests to &lt;code&gt;/llms.txt&lt;/code&gt; were statistically negligible among the user agents that actually drive citations, such as GPTBot, ClaudeBot, and PerplexityBot, meaning most AI systems answering shopping questions in 2026 simply aren't reading it yet.&lt;/p&gt;

&lt;p&gt;The pragmatic move for a Shopify store: add a lightweight llms.txt if it takes an hour, since there's no real downside, but don't treat it as a substitute for the fundamentals above. Structured data and feed hygiene are doing the actual work right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Should Product and Blog Content Be Written So AI Engines Want to Quote It?
&lt;/h2&gt;

&lt;p&gt;Generative engines retrieve short passages, not whole pages, so the unit of content that gets cited is a sentence or two, not a paragraph. Research from Princeton, Georgia Tech, and IIT Delhi found that content optimized for this pattern achieved measurably higher visibility in AI-generated answers, with the biggest single gains coming from adding verifiable statistics to a passage and from leading with a definition-first sentence rather than a scene-setting one, according to &lt;a href="https://red-engage.com/blog/generative-engine-optimization" rel="noopener noreferrer"&gt;a summary of that research&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What that looks like in practice on a Shopify blog or PDP:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lead with the answer.&lt;/strong&gt; Put the direct, factual sentence first in every section, something like "A composting bin needs airflow on at least two sides," before the supporting explanation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make each paragraph stand alone.&lt;/strong&gt; Assume an AI system will lift that one paragraph out of context and quote it. Don't rely on "as mentioned above."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use question-style H2s.&lt;/strong&gt; "What size composting bin do I need for a family of four?" mirrors how people actually phrase prompts, far more than "Composting Bin Sizes."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cite something verifiable.&lt;/strong&gt; A specific number, a named study, or a dated fact gets picked up far more often than a general claim.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refresh the page.&lt;/strong&gt; AI engines weigh recency when choosing which source to cite, so a guide last touched in 2023 loses ground to a competitor's 2026 update on the same topic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A Practical GEO Checklist for Shopify Merchants
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Audit every product page for &lt;code&gt;Offer&lt;/code&gt;, &lt;code&gt;AggregateRating&lt;/code&gt;, &lt;code&gt;MerchantReturnPolicy&lt;/code&gt;, and &lt;code&gt;OfferShippingDetails&lt;/code&gt; schema, not just basic Product markup.&lt;/li&gt;
&lt;li&gt;Check your Google Merchant Center and Bing Merchant Center feeds for stale "in stock" flags. This is one of the fastest ways to get a feed quality-flagged and dropped from AI shopping results.&lt;/li&gt;
&lt;li&gt;Confirm your robots.txt isn't accidentally blocking GPTBot, PerplexityBot, or OAI-SearchBot. Cloudflare's default configuration change in 2025 caused a wave of sites to block AI bots without realizing it.&lt;/li&gt;
&lt;li&gt;Make sure product pages render server-side. AI crawlers generally don't execute heavy client-side JavaScript the way a human browser does.&lt;/li&gt;
&lt;li&gt;Write full, specific product descriptions. A 15-word description gives an AI system almost nothing to reason over when it's matching your product to a conversational query.&lt;/li&gt;
&lt;li&gt;Republish or refresh cornerstone guides at least twice a year with a visible "last updated" date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuuo49a4832911h3f3ceh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuuo49a4832911h3f3ceh.png" alt=" " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Should Actually Run This Audit?
&lt;/h2&gt;

&lt;p&gt;None of the above is exotic engineering, but it does touch theme templates, feed configuration, and content strategy at the same time, which is exactly where things get missed on a live store. This is the kind of cross-cutting technical and content work a team of &lt;a href="https://www.lucentinnovation.com/services/shopify-expert-agency" rel="noopener noreferrer"&gt;dedicated Shopify specialists&lt;/a&gt; is set up to audit and implement properly, because it means checking schema output against the live theme, not just the store's settings panel, and tying that back into how product copy is actually written.&lt;/p&gt;

&lt;p&gt;If you're not sure where your own store stands on any of the checklist above, that's usually the first thing worth having someone independently look at before you spend a quarter chasing individual AI platforms one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does GEO replace traditional SEO for a Shopify store?&lt;/strong&gt;&lt;br&gt;
No. GEO builds on the same technical foundation as SEO (crawlability, structured data, fast pages) and adds a layer on top for how AI systems retrieve and cite content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which AI shopping platform should a Shopify merchant prioritize first?&lt;/strong&gt;&lt;br&gt;
Start with your Google Merchant Center feed. It feeds Google Shopping, Google AI Mode, and indirectly a large share of ChatGPT Shopping's results, so fixing it once pays off across multiple platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is llms.txt required for a Shopify store?&lt;/strong&gt;&lt;br&gt;
No. It's optional and low-cost to add, but current data suggests the major AI shopping crawlers aren't reading it in meaningful volume yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does it take to see AI citations after making these changes?&lt;/strong&gt;&lt;br&gt;
Perplexity, which relies on real-time web retrieval, can pick up freshly published or updated content within days. Feed-based platforms like ChatGPT Shopping and Google AI Mode typically lag behind the next feed refresh cycle, which can take longer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you checked whether your own product pages actually render their schema correctly once the theme's JavaScript runs? Curious what others are finding when they audit their live Shopify stores for this. Drop what you find in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>shopifyseo</category>
      <category>ai</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Shopify Checkout UI Extension Development: A Step-by-Step Guide (2026)</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Tue, 21 Jul 2026 07:05:17 +0000</pubDate>
      <link>https://dev.to/lucy1/shopify-checkout-ui-extension-development-a-step-by-step-guide-2026-441f</link>
      <guid>https://dev.to/lucy1/shopify-checkout-ui-extension-development-a-step-by-step-guide-2026-441f</guid>
      <description>&lt;p&gt;Shopify checkout UI extension development means building a small, sandboxed React or Preact component that Shopify renders inside a fixed slot in checkout, such as a block on the Thank you page or a static area next to the cart line items. This guide scaffolds one from a real Shopify CLI project, reads and writes checkout data through the current &lt;code&gt;shopify&lt;/code&gt; global object, tests it locally, and deploys it under the platform's actual constraints. By the end you'll have a working delivery-instructions extension and a clear list of the places these extensions tend to fail once real customers start using them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You're Building (and What This Guide Skips)
&lt;/h2&gt;

&lt;p&gt;The extension in this walkthrough adds a text field to checkout where a customer can leave a note for the courier, and it shows the current order subtotal above the field. That's deliberately small. It exercises the three things almost every checkout UI extension needs: a target, a read from the &lt;code&gt;shopify&lt;/code&gt; object, and a write back to the order.&lt;/p&gt;

&lt;p&gt;This guide does not cover Shopify Functions (server-side discount, shipping, and payment logic), the Branding API (colors, fonts, and layout), or post-purchase upsell offers. Those are real parts of checkout extensibility, but they solve different problems and deserve their own walkthroughs. Shopify maintains its own &lt;a href="https://shopify.dev/docs/apps/build/checkout/fields-banners/add-field" rel="noopener noreferrer"&gt;custom fields tutorial&lt;/a&gt; covering a similar delivery-instructions pattern, worth a look once you want the officially maintained reference version alongside this one. The next section explains how to tell which one you actually need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkout UI Extensions vs. Shopify Functions: Which One Do You Actually Need?
&lt;/h2&gt;

&lt;p&gt;If the customer needs to see or interact with something, you want a UI extension. If you need to change a price, a discount, a shipping rate, or which payment methods appear, you want a Function. UI extensions render interface inside a sandboxed environment; Functions run server-side with no rendering at all, according to &lt;a href="https://shopify.dev/docs/apps/build/checkout" rel="noopener noreferrer"&gt;Shopify's documentation on apps in checkout&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Checkout UI Extensions&lt;/th&gt;
&lt;th&gt;Shopify Functions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Renders visible UI&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs where&lt;/td&gt;
&lt;td&gt;Client-side, inside a Web Worker sandbox&lt;/td&gt;
&lt;td&gt;Server-side, on Shopify's infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use&lt;/td&gt;
&lt;td&gt;Custom fields, banners, upsell blocks, trust badges&lt;/td&gt;
&lt;td&gt;Discount logic, shipping rate changes, payment method ordering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data access&lt;/td&gt;
&lt;td&gt;Reads/writes cart and checkout data through the &lt;code&gt;shopify&lt;/code&gt; object&lt;/td&gt;
&lt;td&gt;Reads structured input, returns structured output, no direct cart mutation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan requirement&lt;/td&gt;
&lt;td&gt;Available on all plans; the information, shipping, and payment steps still require Shopify Plus&lt;/td&gt;
&lt;td&gt;Available broadly, most often paired with Plus-tier checkout customization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Teams often reach for a UI extension when the real problem is a Function, which is why a custom field ends up trying to also silently change a shipping rate through a side channel. Keep the two separate. If your logic doesn't need to be seen, it probably belongs in a Function instead.&lt;/p&gt;

&lt;p&gt;It's also worth knowing what these two tools replaced. Shopify Scripts, the old way to write custom discount and shipping logic for Plus stores, stopped executing on June 30, 2026, after running alongside checkout extensions during a transition window that &lt;a href="https://shopify.dev/docs/apps/build/checkout#upgrade" rel="noopener noreferrer"&gt;Shopify's own documentation confirms&lt;/a&gt;. Anything still living in an old Script needs to move to a Function now, not a UI extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js and a recent Shopify CLI install&lt;/li&gt;
&lt;li&gt;A Partner account and a development store with checkout extensibility enabled&lt;/li&gt;
&lt;li&gt;An existing custom or public app to attach the extension to (or scaffold a new one with &lt;code&gt;shopify app init&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set the API version to the latest stable release in your configuration file rather than pinning to whatever version a tutorial used months ago. Each stable version stays supported for a minimum of 12 months, and the CLI blocks deploys against versions older than that window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaffold the Extension
&lt;/h2&gt;

&lt;p&gt;From inside your app directory, generate the extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
shopify app generate extension &lt;span class="nt"&gt;--template&lt;/span&gt; checkout_ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a folder with a &lt;code&gt;shopify.extension.toml&lt;/code&gt; configuration file and a templated JSX entry point. Edit the TOML to point at a block target, which is the placement type merchants can position themselves using the checkout editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;api_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2026-07"&lt;/span&gt;

&lt;span class="nn"&gt;[[extensions]]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ui_extension"&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Delivery Notes"&lt;/span&gt;
&lt;span class="py"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"delivery-notes"&lt;/span&gt;
&lt;span class="py"&gt;uid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"generate-this-with-shopify-cli"&lt;/span&gt;

  &lt;span class="nn"&gt;[[extensions.targeting]]&lt;/span&gt;
  &lt;span class="py"&gt;target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"purchase.checkout.block.render"&lt;/span&gt;
  &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./src/DeliveryNotes.jsx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Block targets are one of &lt;a href="https://shopify.dev/docs/api/checkout-ui-extensions/2026-07/targets" rel="noopener noreferrer"&gt;three target types&lt;/a&gt;. Static targets render automatically in a fixed spot, like right after the cart line items, and can't be repositioned. Runnable targets don't render anything at all; they fire in response to an event, such as a keystroke in an address field, and return data. Block targets sit in between: merchants place them through the &lt;a href="https://shopify.dev/docs/api/checkout-ui-extensions/latest" rel="noopener noreferrer"&gt;checkout editor&lt;/a&gt;, and up to three extensions can share the same slot. The &lt;a href="https://shopify.dev/docs/api/shopify-cli" rel="noopener noreferrer"&gt;Shopify CLI&lt;/a&gt; documentation covers the full set of scaffold and dev-server flags if you need to target a specific store or app configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read Checkout Data With the &lt;code&gt;shopify&lt;/code&gt; Global Object
&lt;/h2&gt;

&lt;p&gt;Inside the extension, Shopify injects a global &lt;code&gt;shopify&lt;/code&gt; object that exposes checkout data as properties. There's no &lt;code&gt;import&lt;/code&gt; needed for it: it's simply available at runtime, similar to how &lt;code&gt;window&lt;/code&gt; behaves in a normal browser page, except this one runs inside a Web Worker with no DOM access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@shopify/ui-extensions/preact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preact/hooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DeliveryNotes&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DeliveryNotes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setNote&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shopify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtotalAmount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;stack&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"base"&lt;/span&gt; &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Delivery instructions&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Subtotal: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currencyCode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;text-field&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Leave a note for the courier"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;s&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;shopify.cost.subtotalAmount&lt;/code&gt; updates automatically as the cart changes, and reading it doesn't cost you anything in terms of rate limits since it's a read, not a write. The &lt;code&gt;s-stack&lt;/code&gt;, &lt;code&gt;s-heading&lt;/code&gt;, &lt;code&gt;s-text&lt;/code&gt;, and &lt;code&gt;s-text-field&lt;/code&gt; elements are web components from Shopify's Polaris-based checkout component library, not raw HTML, so they inherit the store's checkout styling without extra CSS work on your part.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;shopify app dev&lt;/code&gt; at this point to preview the extension live on your dev store before writing a single line of persistence logic. Confirming the field renders and the subtotal updates is the cheapest bug you'll ever catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write Changes Back to the Order
&lt;/h2&gt;

&lt;p&gt;Reading data is half the job. The other half is persisting what the customer typed, which happens through &lt;code&gt;applyAttributeChange&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;setNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;shopify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyAttributeChange&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updateAttribute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deliveryInstructions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap this in for the inline &lt;code&gt;onChange&lt;/code&gt; in the component above. &lt;code&gt;applyAttributeChange&lt;/code&gt; returns a promise that resolves once Shopify has applied the change and the corresponding property has updated, and the attribute becomes visible on the order in the admin once it's placed.&lt;/p&gt;

&lt;p&gt;Two boundaries matter here. First, extensions that fire too many attribute or metafield changes in a short window get rate-limited for the rest of that buyer's session, so batch related writes with &lt;code&gt;Promise.all&lt;/code&gt; instead of firing one call per keystroke. Second, an attribute set this way lands on the order object, not inside the payment step; if you need something reflected in pricing or payment method availability, that's a Function's job, not this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the Extension Before It Touches Real Checkouts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;shopify app dev&lt;/code&gt; gives you a live preview against a real dev store, and it reloads automatically as you edit. That covers manual testing. As of API version &lt;code&gt;2026-04&lt;/code&gt;, Shopify also ships &lt;code&gt;@shopify/ui-extensions-tester&lt;/code&gt; for writing actual unit tests against your extension code, which matters once the component grows past a single field and you want regression coverage without opening a browser every time.&lt;/p&gt;

&lt;p&gt;Two checkpoints are worth running before you touch a production store:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm the field appears in the correct slot using the checkout editor's preview, since block target placement is merchant-configurable and easy to get wrong during first setup.&lt;/li&gt;
&lt;li&gt;Place a real test order and check that the attribute shows up on the order detail page in the admin. If it's missing, the write silently failed or the promise was never awaited, not a rendering problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deploy It, and Respect the 64 KB Ceiling
&lt;/h2&gt;

&lt;p&gt;Deployment is a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shopify app deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI builds your extension bundle and uploads it to Shopify. There's a hard constraint worth planning around early: a compiled UI extension bundle can't exceed 64 KB, and Shopify enforces this at deploy time. A single text field extension won't come close, but adding a chart library or a large icon set will get you there faster than expected. &lt;a href="https://shopify.dev/docs/apps/build/app-extensions#analyzing-bundle-size" rel="noopener noreferrer"&gt;Analyze your bundle size&lt;/a&gt; before you're surprised by a failed deploy the day before launch.&lt;/p&gt;

&lt;p&gt;If your extension needs more than the default sandbox allows, such as calling your own backend or collecting SMS marketing consent, you declare that explicitly in the TOML under &lt;a href="https://shopify.dev/docs/apps/build/checkout/capabilities" rel="noopener noreferrer"&gt;&lt;code&gt;[extensions.capabilities]&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;network_access&lt;/code&gt;, &lt;code&gt;api_access&lt;/code&gt;, &lt;code&gt;collect_buyer_consent&lt;/code&gt;, &lt;code&gt;block_progress&lt;/code&gt;). Requesting a capability you don't end up using is a common reason review takes longer than expected, so keep the list matched to what the code actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Do Checkout Extensions Actually Break in Production?
&lt;/h2&gt;

&lt;p&gt;Two failure patterns show up more than any other.&lt;/p&gt;

&lt;p&gt;The first is a mismatch between an extension's declared &lt;code&gt;api_version&lt;/code&gt; and the version a linked extension expects, most commonly on payment-related targets. One developer on the &lt;a href="https://community.shopify.com/t/building-a-credit-card-payments-extension-with-ui-extensibility/401381" rel="noopener noreferrer"&gt;Shopify community forum&lt;/a&gt; hit a crash where checkout failed with a &lt;code&gt;TypeError&lt;/code&gt; before their React component even rendered, tied to a payment extension linked to a UI extension through &lt;code&gt;ui_extension_handle&lt;/code&gt;. The lesson generalizes: when two extensions are wired together, verify the target string and API version match exactly, and don't assume a typo in a config field will surface as a readable error. It often surfaces as a crash somewhere upstream of your own code.&lt;/p&gt;

&lt;p&gt;The second is the sandbox itself. The extension runs inside a Web Worker with no access to &lt;code&gt;window&lt;/code&gt; or the DOM, which means a third-party error-reporting tool that assumes a normal browser environment will silently fail to initialize unless you disable its default integrations and attach &lt;code&gt;error&lt;/code&gt; and &lt;code&gt;unhandledrejection&lt;/code&gt; listeners manually. If you're not seeing errors you know are happening, this is usually why.&lt;/p&gt;

&lt;p&gt;Neither failure mode is exotic. Both come from treating a sandboxed, upgrade-safe environment as if it were a regular webpage, which it deliberately isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build It Yourself or Bring in a Shopify Team?
&lt;/h2&gt;

&lt;p&gt;A single custom field like the one in this guide is a reasonable first project for a developer who's comfortable with React and hasn't touched Shopify's extension model before; budget a day or two including the review cycle. The calculation changes once you're coordinating multiple targets, metafields across international markets, post-purchase offers, or a Scripts-to-Functions migration against a hard deadline. That's less a coding problem than a project-management one, and it's the same reasoning Shopify gives store owners directly in its own upgrade guidance: build the extension yourself, or bring in a service partner to build it for you.&lt;/p&gt;

&lt;p&gt;For merchants in that second situation, &lt;a href="https://www.lucentinnovation.com/specialists/hire-shopify-developers" rel="noopener noreferrer"&gt;Lucent Innovation's Shopify development team&lt;/a&gt; handles exactly this kind of checkout migration and extension work as part of its Shopify Plus practice. Either path is legitimate. The point of this guide is that you now have enough to make that call with a working extension in front of you, not a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;You've got a checkout UI extension that reads live cart data and writes an attribute back to the order, scaffolded, tested, and deployed under the real constraints Shopify enforces at build time. The natural next step is a Shopify Function if your use case needs to touch pricing or shipping logic rather than just displaying something.&lt;/p&gt;

&lt;p&gt;Have you run into the Web Worker sandbox limitations while wiring up error tracking, or found a cleaner pattern for requesting capabilities without triggering a longer review? Drop it in the comments, it's the kind of detail that doesn't make it into the docs.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From Liquid to Hydrogen: A Developer's Honest Guide to Going Headless on Shopify</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Wed, 15 Jul 2026 06:26:47 +0000</pubDate>
      <link>https://dev.to/lucy1/from-liquid-to-hydrogen-a-developers-honest-guide-to-going-headless-on-shopify-2k98</link>
      <guid>https://dev.to/lucy1/from-liquid-to-hydrogen-a-developers-honest-guide-to-going-headless-on-shopify-2k98</guid>
      <description>&lt;p&gt;Most Shopify stores still run on Liquid. That's true today, and it will stay true for a while. It's fast out of the box, cheap to run, and there's nothing extra to host.&lt;/p&gt;

&lt;p&gt;Hydrogen is different. It's Shopify's own React framework, built for stores that need more control over the frontend than Liquid can give.&lt;/p&gt;

&lt;p&gt;This post walks through both. No sales pitch, just the real trade-offs, some code, and an honest answer to the question people keep asking: when is it actually worth going headless?&lt;/p&gt;

&lt;h2&gt;
  
  
  What Liquid Gets Right (and Where It Runs Out)
&lt;/h2&gt;

&lt;p&gt;Liquid is Shopify's own template language. Tobias Lütke, one of Shopify's founders, built it, and it's been running real stores since 2006. That's longer than most of us have been writing code for a living.&lt;/p&gt;

&lt;p&gt;Liquid is also strict on purpose. Merchants can edit their own theme files, so the language can't do everything a full programming language can do. It has to stay safe, since store owners are the ones typing into it, not developers.&lt;/p&gt;

&lt;p&gt;That limit is a good thing, not a flaw. It's why a small business owner can tweak their homepage at 11pm without breaking checkout.&lt;/p&gt;

&lt;p&gt;For most stores, Liquid is still the smart choice. It renders right on Shopify's servers. No extra hosting bill. No build step to babysit. No framework update to plan around every few months.&lt;/p&gt;

&lt;p&gt;Online Store 2.0 made this even better. JSON templates and drag-and-drop sections gave merchants a lot of the flexibility that used to send people looking for custom builds.&lt;/p&gt;

&lt;p&gt;So where does Liquid stop being enough? Usually it's one of these three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The frontend needs to act like an app. Think shared cart state across brands, heavy interactive UI, or checkout flows that need custom logic.&lt;/li&gt;
&lt;li&gt;The brand needs one design system across more than the store. A marketing site, a mobile app, and the storefront all sharing the same components.&lt;/li&gt;
&lt;li&gt;The dev team just knows React better than Liquid, and building in Liquid would slow them down more than it helps anyone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What "Going Headless" Actually Means
&lt;/h2&gt;

&lt;p&gt;People throw the word "headless" around a lot. Let's slow down and define it properly.&lt;/p&gt;

&lt;p&gt;On Shopify, going headless means you split the frontend from the backend. The frontend is what shoppers see and click. The backend holds the product catalog, cart, checkout, and orders. Instead of Liquid rendering pages on Shopify's servers, your own app fetches data through an API and renders it however you want.&lt;/p&gt;

&lt;p&gt;That API is called the Storefront API. It's a GraphQL endpoint, and it isn't locked to one framework. You can call it from a plain Node app, from Next.js, or from Shopify's own Hydrogen.&lt;/p&gt;

&lt;p&gt;Here's the part most people skip past. Going headless is really three choices bundled into one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which framework you build the frontend in&lt;/li&gt;
&lt;li&gt;Which API you pull your data from&lt;/li&gt;
&lt;li&gt;Where you host the result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These don't have to travel together. Hydrogen can run on hosts other than Shopify's own Oxygen. And Oxygen can host things that have nothing to do with Hydrogen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inside Hydrogen: What's New in 2026
&lt;/h2&gt;

&lt;p&gt;Hydrogen has changed shape more times than most frameworks manage to survive.&lt;/p&gt;

&lt;p&gt;It launched in 2022. Then Shopify bought the Remix team and rebuilt Hydrogen on top of Remix. Then Remix's routing engine split off into its own project, React Router 7, and Hydrogen moved onto that instead.&lt;/p&gt;

&lt;p&gt;Right now, the stable version of Hydrogen runs on React Router v7 and TypeScript. It tracks the Storefront API and Customer Account API on a schedule, and that schedule updates every quarter.&lt;/p&gt;

&lt;p&gt;That update schedule is worth planning for. Hydrogen and hydrogen-react are tied to specific versions of these APIs, and since Shopify updates them every three months, breaking changes can show up every three months too. Budget time for that. It's a real, ongoing cost, not a one-time thing you fix and forget.&lt;/p&gt;

&lt;p&gt;The bigger news is what comes next. In the middle of 2026, Shopify showed off a rebuilt version of Hydrogen. The old version asked you to adopt the whole framework. The new one is more like a toolkit you bring into whatever stack you already have.&lt;/p&gt;

&lt;p&gt;This new preview has a core written in plain JavaScript, with thin adapters that make it feel native to whatever framework your team uses. React support ships first. Vue and Svelte support are on the way.&lt;/p&gt;

&lt;p&gt;It also ships with something Shopify calls agent skills. These are structured instructions that let a coding agent set up the Storefront API client, cart, product pages, and checkout hooks for you, in whatever framework you picked. That's a shift from copying boilerplate by hand.&lt;/p&gt;

&lt;p&gt;Whatever comes next, the current stable release is what real stores run today, and it holds up well. It uses the open source React Router framework, supports optimistic UI and nested routes, and it deploys for free on Oxygen, Shopify's own hosting network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Liquid vs. Hydrogen: A Straight Comparison
&lt;/h2&gt;

&lt;p&gt;Neither one wins in every case. Here's how they actually stack up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Liquid (Online Store 2.0)&lt;/th&gt;
&lt;th&gt;Hydrogen (React Router 7)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Included, no extra setup&lt;/td&gt;
&lt;td&gt;Free on Oxygen, or any Node/edge host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Liquid plus JSON templates&lt;/td&gt;
&lt;td&gt;TypeScript and React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills your team needs&lt;/td&gt;
&lt;td&gt;Theme development, CSS, a bit of JS&lt;/td&gt;
&lt;td&gt;Solid React skills, plus GraphQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can merchants edit it themselves?&lt;/td&gt;
&lt;td&gt;Yes, through the theme editor&lt;/td&gt;
&lt;td&gt;Not easily. You'd need to add a CMS layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App compatibility&lt;/td&gt;
&lt;td&gt;Wide. Most apps just work&lt;/td&gt;
&lt;td&gt;Mixed. Some apps built for Liquid themes need custom wiring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed out of the box&lt;/td&gt;
&lt;td&gt;Fast by default&lt;/td&gt;
&lt;td&gt;Fast only if someone tunes it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Most D2C and B2B stores&lt;/td&gt;
&lt;td&gt;Complex catalogs, custom UX, multi-brand builds, AI-driven shopping&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That speed row needs a warning label, because it's the claim people stretch the most. A well-tuned Hydrogen store can absolutely beat a bloated Liquid theme. But "well-tuned" is carrying a lot of weight in that sentence. It means someone who understands edge caching and streaming rendering is actively setting it up, not that Hydrogen wins by default.&lt;/p&gt;

&lt;p&gt;Google is pretty clear on this point too. Core Web Vitals are supposed to be measured out in the real world, based on how real users experience a page, and site owners shouldn't have to become performance experts just to know if their site feels fast. That's exactly the bar a rushed, untuned headless build usually misses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Same Data Fetch, Two Ways
&lt;/h2&gt;

&lt;p&gt;Let's make this less abstract. Here's the same small task done in Liquid, then in Hydrogen: pull a product's title and price and show them on the page.&lt;/p&gt;

&lt;p&gt;In Liquid, the &lt;code&gt;product&lt;/code&gt; object is already sitting there, ready to use. No API call needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&amp;lt;h1&amp;gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&amp;lt;/h1&amp;gt;
&amp;lt;span class="price"&amp;gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;price&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;money&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Hydrogen, you write a loader that queries the Storefront API's GraphQL endpoint, then you render what comes back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/products.$handle.jsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storefront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`#graphql
    query Product($handle: String!) {
      product(handle: $handle) {
        title
        priceRange {
          minVariantPrice { amount currencyCode }
        }
      }
    }`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;loaderData&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;loaderData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priceRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minVariantPrice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priceRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minVariantPrice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currencyCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same end result. Very different amount of code to own.&lt;/p&gt;

&lt;p&gt;The Liquid version has no build step and nothing to ship to the browser beyond the rendered HTML. The Hydrogen version gives you full control over that markup and lets you mix it with any React component in your app, but you're the one writing and maintaining the query, the loader, and the rendering logic from here on out.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Headless Actually Pays Off (and When It's Overkill)
&lt;/h2&gt;

&lt;p&gt;Ask yourself these questions before you commit to a headless rebuild.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does your catalog or user experience need logic Liquid genuinely can't handle?&lt;/strong&gt; Product configurators, live inventory pulled from several warehouses feeding one custom availability rule, or a shopping flow that behaves more like an app than a page. These are real reasons to go headless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can your team actually maintain a framework that breaks every quarter?&lt;/strong&gt; Hydrogen moves on the same calendar as Shopify's APIs. Someone on your team needs to own that upgrade cycle as an ongoing job, not a one-off project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do the apps you rely on even work headless?&lt;/strong&gt; A large chunk of the Shopify App Store assumes a Liquid theme is injecting a script tag onto the page. Some apps talk to the Storefront API cleanly. Others need custom work, or don't support headless setups at all. Find this out before you start building, not after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is AI-driven shopping actually the goal?&lt;/strong&gt; If your roadmap includes a shopping assistant or an AI agent that browses your catalog on a customer's behalf, that's a case where Hydrogen's direct connection to the Storefront API earns its complexity. That GraphQL layer is exactly what those systems need to read.&lt;/p&gt;

&lt;p&gt;If most of your answers land on "not really," a solid Liquid theme built on Online Store 2.0 is probably the faster, cheaper, and safer path to the same goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Teams Actually Land on an Answer
&lt;/h2&gt;

&lt;p&gt;In practice, this decision rarely comes down to which framework someone likes more. It comes down to how complex the catalog is, how deep the team's React skills go, and how much of the App Store ecosystem the store leans on.&lt;/p&gt;

&lt;p&gt;Teams that get this wrong usually make the call based on what's trendy, in the abstract, instead of checking it against their own app stack and how their content team actually works day to day.&lt;/p&gt;

&lt;p&gt;When Lucent Innovation's &lt;a href="https://www.lucentinnovation.com/services/shopify-expert-agency" rel="noopener noreferrer"&gt;Shopify store experts&lt;/a&gt; sit down with a merchant to answer this exact question, the audit almost always starts with app compatibility and how much merchants need to edit the site themselves. Architecture comes later. Those two things alone usually rule out one option before anyone even opens a performance benchmark.&lt;/p&gt;

&lt;p&gt;However the decision lands, the same rule holds up: pick the setup your team can actually build and keep running for the next few years. Not the one that sounds best in a slide deck.&lt;/p&gt;




&lt;p&gt;Have you shipped a Hydrogen store, or looked into one and decided against it? I'd like to hear what actually tipped the decision for your team.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>react</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Shopify Scripts Are Dead — Here's How to Migrate to Shopify Functions Before June 30, 2026</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:14:33 +0000</pubDate>
      <link>https://dev.to/lucy1/shopify-scripts-are-dead-heres-how-to-migrate-to-shopify-functions-before-june-30-2026-3lmg</link>
      <guid>https://dev.to/lucy1/shopify-scripts-are-dead-heres-how-to-migrate-to-shopify-functions-before-june-30-2026-3lmg</guid>
      <description>&lt;p&gt;If you're running a Shopify Plus store with custom Scripts, you have 4 days left.&lt;br&gt;
June 30, 2026 is Shopify's hard deprecation date for Shopify Scripts — the Ruby-based customization layer powering custom discounts, shipping rules, and payment logic for thousands of merchants. After this date, every Script stops executing. Silently. With no fallback. Your checkout reverts to Shopify's defaults as if your custom logic never existed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Critical: As of April 15, 2026, the Script Editor is already locked. You can no longer edit or publish Scripts. Any Script still running in production is frozen code — bugs cannot be patched. June 30 is when execution stops entirely. If you haven't started migrating, you are running unmodifiable code in production right now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't a "we'll get to it eventually" situation. It's a breaking change with a hard deadline days away.&lt;br&gt;
In this guide I'll walk you through:&lt;/p&gt;
&lt;h3&gt;
  
  
  What's actually changing and why
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The direct replacement for each Script type&lt;/li&gt;
&lt;li&gt;A step-by-step migration path with corrected CLI commands&lt;/li&gt;
&lt;li&gt;Real code examples showing before and after&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's move fast.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Are Shopify Scripts?
&lt;/h2&gt;

&lt;p&gt;Shopify Scripts were introduced as a Shopify Plus-only feature that let developers write Ruby code to customize the cart and checkout experience. Three types existed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line Item Scripts —&lt;/strong&gt; modify prices, apply discounts, bundle logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipping Scripts —&lt;/strong&gt; customize shipping rates, hide or rename options&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Scripts —&lt;/strong&gt; show or hide payment methods based on conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They were powerful for their time, but they carried serious limitations: Ruby-only, Plus-only, slow to test, no version control, and fundamentally incompatible with Shopify's Checkout Extensibility architecture that replaced &lt;code&gt;checkout.liquid&lt;/code&gt; for Plus merchants in 2023–2024.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Shopify Is Deprecating Them
&lt;/h2&gt;

&lt;p&gt;Three reasons, in plain terms:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Checkout Extensibility replaced checkout.liquid —&lt;/strong&gt; Scripts are architecturally incompatible with the new checkout model&lt;br&gt;
&lt;strong&gt;2. Performance —&lt;/strong&gt; Ruby Scripts ran server-side with cold-start delays. Shopify Functions compile to WebAssembly and run under a strict 5ms execution cap. Rust Functions typically execute in 3–5ms; JavaScript Functions run 10–30ms in real-world use and should be used only for simpler logic&lt;br&gt;
&lt;strong&gt;3. Platform-wide access —&lt;/strong&gt; Shopify Functions are available to all plan levels via installed apps, not just Shopify Plus subscribers&lt;/p&gt;
&lt;h2&gt;
  
  
  Scripts vs. Functions: What Actually Changed
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdo5foeegvjkal04rj3h0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdo5foeegvjkal04rj3h0.png" alt=" " width="800" height="514"&gt;&lt;/a&gt;&lt;br&gt;
Here's the architectural shift. Scripts were a Plus-only workaround bolted onto an older platform. Functions are native, WebAssembly-powered infrastructure available on every plan.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Gets Replaced by What?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feja1fn1ccpjpwyujjc1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feja1fn1ccpjpwyujjc1j.png" alt=" " width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter Shopify Functions
&lt;/h2&gt;

&lt;p&gt;Shopify Functions are the modern replacement. You write them in JavaScript/TypeScript or Rust — the two officially maintained, first-class languages in 2026. Any language that compiles to WebAssembly is technically supported, but Rust and JavaScript are the only paths with active Shopify CLI tooling and official support.&lt;/p&gt;

&lt;p&gt;Choose based on your use case:&lt;br&gt;
&lt;strong&gt;1. JavaScript/TypeScript —&lt;/strong&gt; good for prototyping, simpler discount logic, teams without Rust experience. Compiled via Shopify's Javy toolchain.&lt;br&gt;
&lt;strong&gt;2. Rust —&lt;/strong&gt; recommended for complex logic, large carts, public apps, or any Function near the 256KB binary size limit. Runs 3–5ms versus 10–30ms for JavaScript.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Platform limits (as of 2026):&lt;/strong&gt; Each store can run a maximum of 5 Discount Functions, 1 Cart Transform Function, and 5 Validation Functions. Plan your migration with these caps in mind if you have many Scripts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Migration: Step by Step
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Audit your current Scripts
&lt;/h3&gt;

&lt;p&gt;Go to Shopify Admin → Apps → Script Editor. The editor is now read-only (locked since April 15), but you can still view all active Scripts, export the customizations report, and read the source logic.&lt;br&gt;
List every active Script, what business rule it enforces, and every edge case it handles. &lt;/p&gt;

&lt;p&gt;Pay attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this Script still being used in production?&lt;/li&gt;
&lt;li&gt;What customer-facing behaviour does it produce?&lt;/li&gt;
&lt;li&gt;Are there conditional rules — customer tags, order thresholds, product exclusions, B2B rules?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the Shopify Scripts customizations report (available from the Script Editor page) to export the full list automatically.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Set up your Shopify Functions environment
&lt;/h3&gt;

&lt;p&gt;You need Shopify CLI 4.0 or higher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Verify CLI version
npm install -g @shopify/cli
shopify version  # confirm 4.0+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new app or add a Function extension to an existing one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a new app (correct CLI 4.0 syntax)
npm init @shopify/app@latest

# Or add an extension to an existing app
shopify app generate extension
# Choose: Discount, Cart Transform, Delivery Customization, or Payment Customization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Scaffold the right Function type
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shopify app generate extension
# Select "Discount - Order discounts"    → for order-level discount Scripts
# Select "Discount - Product discounts"  → for variant/product-level logic
# Select "Delivery customization"        → for Shipping Scripts
# Select "Payment customization"         → for Payment Scripts
# Select "Cart transform"                → for bundle/kit Line Item Scripts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Rewrite your logic in JavaScript or Rust
&lt;/h3&gt;

&lt;p&gt;Here's a real before/after for the most common migration case — &lt;strong&gt;10% off all orders over $100&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Before (Ruby — Shopify Script)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input.cart.line_items.each do |line_item|
  if Input.cart.subtotal_price &amp;gt;= Money.new(cents: 100_00)
    line_item.change_line_price(
      line_item.line_price * 0.9,
      message: "10% bulk discount"
    )
  end
end

Output.cart = Input.cart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After (JavaScript — Shopify Order Discount Function)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// run.js — Order Discount Function
export function run(input) {
  const subtotal = parseFloat(
    input.cart.cost.subtotalAmount.amount
  );

  if (subtotal &amp;gt;= 100) {
    return {
      discounts: [
        {
          targets: [
            { orderSubtotal: { excludedVariantIds: [] } }
          ],
          value: { percentage: { value: "10.0" } },
          message: "10% bulk discount"
        }
      ],
      discountApplicationStrategy: "FIRST"
    };
  }

  return {
    discounts: [],
    discountApplicationStrategy: "FIRST"
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business logic is the same. The architecture is fundamentally better — compiled to WebAssembly, version-controlled, deployable via CI/CD, and accessible on all Shopify plans.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Using Rust instead?&lt;/strong&gt; The logic structure is similar. Shopify's CLI scaffolds a full Rust project with the shopify_function crate when you select Rust at the extension type prompt. For complex discount engines with large catalogs, Rust is the safer choice as JavaScript Functions can exceed the 5ms execution cap on heavy carts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 5: Test in a development store
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shopify app dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deploys your Function to a development store in draft mode. Create a discount in Shopify Admin that uses your new Function, then test it across all cart scenarios — especially every edge case from the original Script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Deploy to production and monitor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shopify app deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Link your Function to a discount or customization in Shopify Admin (or via the Admin API). Monitor checkout conversion rate carefully for the first 48 hours after going live. A measurable drop typically signals a missed edge case, not a platform issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Migration Pitfalls
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Scripts that relied on execution order
&lt;/h3&gt;

&lt;p&gt;Scripts ran sequentially and could interact with each other. Functions run independently in parallel. If you had two Line Item Scripts that stacked or modified each other's output, refactor the logic into a single Function or control stacking behaviour explicitly with &lt;code&gt;discountApplicationStrategy&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Bundle logic mapped to the wrong Function type
&lt;/h3&gt;

&lt;p&gt;Bundle discounts in Line Item Scripts map to Cart Transform Functions, not Discount Functions. These are a completely separate extension type — selecting the wrong one at scaffold time means you're building against the wrong API schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Assuming Shipping Scripts are low priority
&lt;/h3&gt;

&lt;p&gt;All three Script types — line item, shipping, and payment — stop executing on June 30. Shipping Scripts are often lower revenue impact than discount Scripts, but if your store hides express shipping for fragile items, shows different carrier options by customer tag, or applies shipping discounts conditionally, those rules disappear on the deadline. Migrate them before June 30, not after.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Edge cases left uncoded
&lt;/h3&gt;

&lt;p&gt;Ruby Scripts accumulated implicit logic over years — gift card exclusions, B2B pricing tiers, free shipping thresholds, variant-level carve-outs. Functions are explicit. Every condition your business requires must be coded. The audit in Step 1 is your safety net here.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. JavaScript Functions on complex carts
&lt;/h3&gt;

&lt;p&gt;JavaScript Functions run 10–30ms in real-world use. Shopify's 5ms execution cap means JavaScript Functions can be aborted on heavy carts, leaving the checkout in its default state. For stores with large catalogs or many line items, write in Rust. For simple rules (e.g. flat percentage discount on all orders), JavaScript is fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Not reading the GraphQL input schema
&lt;/h3&gt;

&lt;p&gt;Each Function type has a defined input schema. You control what data your Function receives by editing &lt;code&gt;run.graphql&lt;/code&gt;. If your logic needs metafields, customer tags, or product attributes, add them to the input query — do not assume they arrive automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What If You're Already Behind?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvplmmdf4uiwu5035vl6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvplmmdf4uiwu5035vl6e.png" alt=" " width="800" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're reading this with four days on the clock and active Scripts still in production, here's the prioritisation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Revenue-critical Scripts first —&lt;/strong&gt; discount Scripts affecting checkout conversion and payment Scripts controlling visible gateways go first&lt;br&gt;
&lt;strong&gt;2. Stub before you optimise —&lt;/strong&gt; a working Function that matches the Script's behaviour is better than a perfectly architected one that isn't deployed&lt;br&gt;
&lt;strong&gt;3. Migrate all Script types before June 30 —&lt;/strong&gt; Line Item, Shipping, and Payment Scripts all stop on the same date. Don't assume Shipping Scripts can wait; schedule them in the same sprint&lt;br&gt;
&lt;strong&gt;4. Use Shopify's migration docs at &lt;a href="https://shopify.dev/" rel="noopener noreferrer"&gt;shopify.dev&lt;/a&gt; —&lt;/strong&gt; the customizations report and Function input schemas are well-documented&lt;/p&gt;

&lt;p&gt;If you have a complex store with multiple interdependent Scripts and the timeline feels impossible, working with a &lt;a href="https://www.lucentinnovation.com/services/shopify-expert-agency" rel="noopener noreferrer"&gt;certified Shopify expert agency&lt;/a&gt; is the fastest path. Teams like Lucent Innovation — 10+ years as a Shopify Plus partner, 12+ years in ecommerce, 5,000+ stores delivered — can run a focused Functions migration sprint with proper QA, significantly faster than doing this under pressure in-house.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Recap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Shopify Scripts deprecate &lt;strong&gt;June 30, 2026&lt;/strong&gt; — no extension, no fallback, no grace period&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Script Editor locked since April 15, 2026&lt;/strong&gt; — existing Scripts are frozen and cannot be modified&lt;/li&gt;
&lt;li&gt;Shopify Functions replace Scripts with WebAssembly execution, all-plan access, and full CI/CD support&lt;/li&gt;
&lt;li&gt;Write Functions in &lt;strong&gt;JavaScript/TypeScript&lt;/strong&gt; (simpler logic) or &lt;strong&gt;Rust&lt;/strong&gt; (complex logic, large carts)&lt;/li&gt;
&lt;li&gt;Per-store limits apply: 5 Discount Functions, 1 Cart Transform, 5 Validation Functions&lt;/li&gt;
&lt;li&gt;Migration path: audit via customizations report → scaffold correct type → rewrite → test → deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with the audit. Everything else follows from there.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Already migrated? What was the trickiest Script to convert? Drop it in the comments — would love to hear real-world edge cases.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About the author:&lt;/strong&gt; This post was written by the engineering team at &lt;a href="https://www.lucentinnovation.com" rel="noopener noreferrer"&gt;Lucent Innovation&lt;/a&gt;, a certified Shopify Plus partner with 10+ years on the platform and 12+ years building and scaling ecommerce stores.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>ecommerce</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Migrate Your Data to Delta Lake: A Simple Guide for Developers</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Tue, 16 Jun 2026 09:31:12 +0000</pubDate>
      <link>https://dev.to/lucy1/migrate-your-data-to-delta-lake-a-simple-guide-for-developers-4g1o</link>
      <guid>https://dev.to/lucy1/migrate-your-data-to-delta-lake-a-simple-guide-for-developers-4g1o</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Delta Lake is like adding safety rules to your data storage. It stops data accidents, lets you see old versions of your data, and keeps everything organized. If you work with big data and Apache Spark, Delta Lake makes your life easier. It uses ACID transactions (fancy words for "data never breaks") and costs way less than old data warehouses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What This Article Covers:&lt;/strong&gt; This post explains what Delta Lake is, why you should move your data to it, and how to start the migration. We look at real problems Delta Lake solves and give you a simple roadmap for getting started. For more in-depth technical details, see Delta Lake Explained on the Lucent Innovation blog.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Old Data Lakes
&lt;/h2&gt;

&lt;p&gt;Imagine you have a giant closet to store files. In the old days, data lakes worked like that. You threw all your data files in there, and it was cheap. But there was a big problem.&lt;/p&gt;

&lt;p&gt;What happens if someone is reading a file while someone else is writing to it? The file could get messed up. What if the computer crashes while saving? You might lose all your work. There was no good way to fix mistakes or go back to how things were before.&lt;/p&gt;

&lt;p&gt;That is the main problem Delta Lake solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Delta Lake?
&lt;/h2&gt;

&lt;p&gt;Delta Lake is a layer of software that sits on top of your data files. Think of it like a smart librarian for your data. It keeps track of every change, makes sure nothing gets broken, and lets you fix mistakes fast.&lt;/p&gt;

&lt;p&gt;The cool part is that Delta Lake does this without charging you a fortune. It runs on cheap cloud storage like Amazon S3 or Azure Blob Storage, just like regular data lakes. But it adds power that usually costs a lot of money.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Features That Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ACID Transactions (No More Broken Data)
&lt;/h3&gt;

&lt;p&gt;ACID is short for Atomicity, Consistency, Isolation, and Durability. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Atomicity:&lt;/strong&gt; Either the whole write happens or none of it happens. No half-finished work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency:&lt;/strong&gt; Data stays clean and organized, no broken records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolation:&lt;/strong&gt; People can read and write at the same time without getting in each other's way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durability:&lt;/strong&gt; Once data is saved, it stays saved. No losing work if the power goes out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In plain English: You never have broken data or weird errors from too many people using the system at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time Travel (Go Back in Time)
&lt;/h3&gt;

&lt;p&gt;Did you delete something by accident? Need to check how data looked last week? Delta Lake remembers everything. You can run a query that shows you your data from any point in the past.&lt;/p&gt;

&lt;p&gt;This is super helpful for audits, fixing mistakes, or checking when something went wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema Enforcement (Keep Data Clean)
&lt;/h3&gt;

&lt;p&gt;A schema is a blueprint that says what columns you have and what type of data goes in each one. Delta Lake watches the door and makes sure bad data never gets in. If someone tries to add data that does not match the blueprint, Delta Lake stops it right there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Updates and Deletes
&lt;/h3&gt;

&lt;p&gt;In regular data lakes, updates and deletes are slow and messy. You have to rewrite whole files. Delta Lake makes this fast and easy. You can change or remove records without rewriting everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Move to Delta Lake?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cost Savings
&lt;/h3&gt;

&lt;p&gt;A big data warehouse from twenty years ago costs a fortune to run. Delta Lake gives you warehouse-level reliability but uses cheap cloud storage underneath. You can save up to 50 times on computing costs while still getting fast answers to your questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed and Trust
&lt;/h3&gt;

&lt;p&gt;Your team can trust the data faster. You spend less time fixing problems and more time using the data. No more mysteries about whether a number is right or wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-Time and Batch in One Place
&lt;/h3&gt;

&lt;p&gt;Some data comes in one batch per day. Some comes in live streams all day long. Delta Lake handles both in the same place, with the same tools. You do not need different systems for different types of data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy Audits and Compliance
&lt;/h3&gt;

&lt;p&gt;Every change is tracked in a log. This is gold when you need to follow rules like GDPR or show customers that their data is safe. You can prove exactly who changed what and when.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Start Your Migration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Check Your Current Setup
&lt;/h3&gt;

&lt;p&gt;Before you move anything, understand what you have now. Answer these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What files do you store? (CSV, JSON, Parquet, something else?)&lt;/li&gt;
&lt;li&gt;How big is your data? (1 GB or 1 TB?)&lt;/li&gt;
&lt;li&gt;Who uses it? (Engineers, analysts, AI models?)&lt;/li&gt;
&lt;li&gt;What problems do you hit most? (Slow updates? Broken data? Hard to audit?)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Start Small
&lt;/h3&gt;

&lt;p&gt;Do not move everything at once. Pick one table or one folder that is not critical. Move it to Delta Lake and run it for a week. See if your team likes it. Break things on purpose to understand how Delta Lake handles problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Set Up Your Infrastructure
&lt;/h3&gt;

&lt;p&gt;Delta Lake works with Databricks (a company built for this), or you can run it open-source with Apache Spark. For most projects, using Databricks is easier because everything just works together.&lt;/p&gt;

&lt;p&gt;If you want to go full open-source, you will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache Spark&lt;/li&gt;
&lt;li&gt;Storage like S3 or Azure&lt;/li&gt;
&lt;li&gt;A way to run the code (like a Linux server)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Copy Your Data Over
&lt;/h3&gt;

&lt;p&gt;For small amounts of data, you can copy directly. For huge amounts, break it into chunks and move one chunk at a time. This way if something breaks, you only fix that chunk, not everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Test Everything
&lt;/h3&gt;

&lt;p&gt;Before you tell everyone to use the new system, run real queries on it. Check that the numbers match the old system. Have your analysts double-check important reports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Switch Over and Watch It
&lt;/h3&gt;

&lt;p&gt;Pick a time when not many people are using the system. Switch everyone to Delta Lake. Have people ready to help if something goes wrong. Watch for problems the first few days.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Keep the Old System as Backup
&lt;/h3&gt;

&lt;p&gt;Even after you switch, keep your old data around for a few weeks. If something goes really wrong, you can go back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Example: Your First Delta Lake Table
&lt;/h2&gt;

&lt;p&gt;If you know Python and Spark, it is super easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Read your data
&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old_data.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Write it as Delta Lake
&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;overwrite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta_table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Now read it back as Delta Lake
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta_table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# See past versions
&lt;/span&gt;&lt;span class="n"&gt;df_yesterday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;versionAsOf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta_table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. Three lines to switch from regular files to Delta Lake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Costs vs. Old Systems
&lt;/h2&gt;

&lt;p&gt;Let's look at numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Cost Per Year&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Broken Data&lt;/th&gt;
&lt;th&gt;Ease&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Old Data Warehouse&lt;/td&gt;
&lt;td&gt;$500K+&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Rare&lt;/td&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regular Data Lake&lt;/td&gt;
&lt;td&gt;$50K&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;Common&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delta Lake&lt;/td&gt;
&lt;td&gt;$50K-100K&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Rare&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Databricks Lakehouse&lt;/td&gt;
&lt;td&gt;$100K-200K&lt;/td&gt;
&lt;td&gt;Very Fast&lt;/td&gt;
&lt;td&gt;Very Rare&lt;/td&gt;
&lt;td&gt;Very Easy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exact cost depends on how much data you have and how much you use it. But the pattern is clear: Delta Lake gives you warehouse reliability at lake prices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Do I need to rewrite all my code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: Not really. If you use Spark SQL or Python with Spark, you mostly use the same code. The main change is using "delta" as the format instead of "parquet" or "csv."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What if my company uses a different system like Spark, Flink, or Kafka?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: Delta Lake works with all of them. It is just a format and a set of rules. Any system that can read Parquet files can work with Delta Lake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is Delta Lake production-ready?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: Yes. Thousands of companies run it in production. It handles petabytes of data every day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How hard is the migration?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A: It depends on your setup. If you have simple CSV or Parquet files, it is easy. If you have a complex system with lots of custom code, it takes more time. Plan for weeks or months, not days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Delta Lake is a great investment for any team that works with big data. It solves real problems and saves money at the same time. Start small, test it out, and see if it works for your team.&lt;/p&gt;

&lt;p&gt;If you want to learn more about the deep technical details like transaction logs, how Delta Lake picks which files to read, and how schema evolution works, check out &lt;a href="https://www.lucentinnovation.com/resources/technology-posts/delta-lake-explained" rel="noopener noreferrer"&gt;Delta Lake Explained&lt;/a&gt; on Lucent Innovation's technology blog. It goes much deeper into these topics.&lt;/p&gt;

&lt;p&gt;The important thing is to start somewhere. Pick one small project. Try Delta Lake. See how it feels. You will probably wonder why you did not switch earlier.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>database</category>
    </item>
    <item>
      <title>How to Speed Up Your Shopify Store in 5 Easy Steps for Better Performance</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Mon, 15 Jun 2026 13:25:34 +0000</pubDate>
      <link>https://dev.to/lucy1/how-to-speed-up-your-shopify-store-in-5-easy-steps-for-better-performance-24gk</link>
      <guid>https://dev.to/lucy1/how-to-speed-up-your-shopify-store-in-5-easy-steps-for-better-performance-24gk</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Most online stores lose customers because of slow loading times. Did you know that if your store takes more than 3 seconds to load, people often leave? A slow Shopify store can cost you money and customers. (Check out more &lt;/p&gt;
&lt;div class="ltag__tag ltag__tag__id__2591"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/ecommerce" class="ltag__tag__link"&gt;ecommerce&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
 strategies on Dev.to)

&lt;p&gt;In this post, we will cover 5 simple things you can do to make your Shopify store faster. These tips work for everyone, from small businesses to large online stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Will Learn
&lt;/h2&gt;

&lt;p&gt;In this article, you will find out about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image optimization methods&lt;/li&gt;
&lt;li&gt;Reducing JavaScript and CSS files&lt;/li&gt;
&lt;li&gt;Using a content delivery network (CDN)&lt;/li&gt;
&lt;li&gt;Caching strategies&lt;/li&gt;
&lt;li&gt;Monitoring your store speed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Optimize Your Images
&lt;/h2&gt;

&lt;p&gt;Large images slow down your store. Images should be as small as possible but still look good.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Images Matter:&lt;/strong&gt;&lt;br&gt;
Images make up most of a website's file size. If you have 20 large product images on one page, your store gets very slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Fix It:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use tools like &lt;strong&gt;TinyPNG&lt;/strong&gt; or &lt;strong&gt;ImageOptim&lt;/strong&gt; to make images smaller. These tools remove extra data from images without making them look bad.&lt;/p&gt;

&lt;p&gt;You can also use Shopify's built-in image compression features. Just upload your images to Shopify and let it handle the resizing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; Use WebP format instead of JPG. WebP images are 25 to 35 percent smaller and look just as good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Minify CSS and JavaScript
&lt;/h2&gt;

&lt;p&gt;Minification means removing extra spaces and characters from your code. This makes files smaller and faster to load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Gets Removed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extra spaces&lt;/li&gt;
&lt;li&gt;Line breaks&lt;/li&gt;
&lt;li&gt;Comments in the code&lt;/li&gt;
&lt;li&gt;Unused characters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most Shopify themes already do this automatically. But if you are building a custom theme, you should check your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools You Can Use:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS Minifier&lt;/li&gt;
&lt;li&gt;JavaScript Minifier&lt;/li&gt;
&lt;li&gt;Shopify's built-in minification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools take your code and make it shorter without changing what it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Use a CDN
&lt;/h2&gt;

&lt;p&gt;A CDN is a content delivery network. It stores your images and files in many locations around the world. When someone visits your store, they get files from the closest location.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you have a customer in Japan and your server is in the USA, they have to download files from far away. With a CDN, a copy of your files sits in Japan too.&lt;/p&gt;

&lt;p&gt;Shopify uses Cloudflare as its CDN, which is really good. Most Shopify plans include CDN automatically, so you probably already have this feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Enable Caching
&lt;/h2&gt;

&lt;p&gt;Caching means saving some information so you do not have to load it again. This makes repeat visits much faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser Caching:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your customer's browser can save images, CSS, and JavaScript on their computer. When they visit again, these files load instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server Caching:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your Shopify server can save product information and page data. This reduces the work the server has to do.&lt;/p&gt;

&lt;p&gt;You can enable caching through Shopify settings or use apps designed for this purpose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Popular Caching Apps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache Cleaner&lt;/li&gt;
&lt;li&gt;Bulk Operations&lt;/li&gt;
&lt;li&gt;Speed Booster&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Test Your Speed
&lt;/h2&gt;

&lt;p&gt;Use Google PageSpeed Insights or GTmetrix to test how fast your store loads. Run tests after you make changes to see what works best.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Test:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Google PageSpeed Insights&lt;/li&gt;
&lt;li&gt;Enter your Shopify store URL&lt;/li&gt;
&lt;li&gt;Click "Analyze"&lt;/li&gt;
&lt;li&gt;Read the report&lt;/li&gt;
&lt;li&gt;Make changes&lt;/li&gt;
&lt;li&gt;Test again&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep testing until your score gets better. Aim for at least 75 out of 100.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;Making your Shopify store faster does not have to be hard. Here are the main points to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimize images with tools like TinyPNG&lt;/li&gt;
&lt;li&gt;Minify your JavaScript and CSS code&lt;/li&gt;
&lt;li&gt;Use a CDN like Cloudflare&lt;/li&gt;
&lt;li&gt;Enable caching for faster repeat visits&lt;/li&gt;
&lt;li&gt;Test your speed regularly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these steps will help you keep customers happy and increase your sales.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A fast Shopify store means more customers and more money. These five steps will help you speed up your store today. Start with image optimization because that gives you the biggest improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What speed tricks do you use on your Shopify store? Share in the comments below.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Google PageSpeed Insights: &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;https://pagespeed.web.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GTmetrix Speed Test: &lt;a href="https://gtmetrix.com/" rel="noopener noreferrer"&gt;https://gtmetrix.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Shopify Performance Guide: &lt;a href="https://shopify.dev/" rel="noopener noreferrer"&gt;https://shopify.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloudflare CDN: &lt;a href="https://www.cloudflare.com/" rel="noopener noreferrer"&gt;https://www.cloudflare.com/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related Dev.to Posts
&lt;/h2&gt;

&lt;p&gt;If you found this helpful, check out these related articles on Dev.to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;div class="ltag__tag ltag__tag__id__7878"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/shopify" class="ltag__tag__link"&gt;shopify&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
 Learn more about Shopify development and ecommerce solutions&lt;/li&gt;
&lt;li&gt;
&lt;div class="ltag__tag ltag__tag__id__677"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/performance" class="ltag__tag__link"&gt;performance&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        Tag for content related to software performance.
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
 Explore other web performance optimization techniques&lt;/li&gt;
&lt;li&gt;
&lt;div class="ltag__tag ltag__tag__id__2591"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/ecommerce" class="ltag__tag__link"&gt;ecommerce&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
 Discover more ecommerce development best practices&lt;/li&gt;
&lt;li&gt;
&lt;div class="ltag__tag ltag__tag__id__8"&gt;
    &lt;div class="ltag__tag__content"&gt;
      &lt;h2&gt;#&lt;a href="https://dev.to/t/webdev" class="ltag__tag__link"&gt;webdev&lt;/a&gt; Follow
&lt;/h2&gt;
      &lt;div class="ltag__tag__summary"&gt;
        Because the internet...
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
 Stay updated with the latest web development trends
Have questions about Shopify performance? Drop them in the comments and I will help you out!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>shopify</category>
      <category>performance</category>
      <category>ecommerce</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What Does an AI Consultant Actually Do? A 2026 Breakdown for Business Leaders</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Fri, 12 Jun 2026 12:59:04 +0000</pubDate>
      <link>https://dev.to/lucy1/what-does-an-ai-consultant-actually-do-a-2026-breakdown-for-business-leaders-2gcc</link>
      <guid>https://dev.to/lucy1/what-does-an-ai-consultant-actually-do-a-2026-breakdown-for-business-leaders-2gcc</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short Answer&lt;/strong&gt;&lt;br&gt;
An AI consultant helps your business figure out where AI can help, what to build, and how to make it actually work without the guesswork or wasted budget. They bridge the gap between cutting-edge technology and real business outcomes. In 2026, with the global AI consulting market valued at over $11 billion and growing at 26% annually, knowing exactly what you're paying for matters more than ever.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Even Is an AI Consultant?
&lt;/h2&gt;

&lt;p&gt;Let's be honest. "AI consultant" sounds like one of those titles that could mean anything.&lt;/p&gt;

&lt;p&gt;It could mean someone who builds models. Or someone who just makes slide decks. Or someone who helps you figure out which tools to buy. The real answer? All three and more.&lt;/p&gt;

&lt;p&gt;An AI consultant is a specialist who helps organizations identify AI opportunities, design solutions, and make sure those solutions actually work in the real world. They are not just coders. They are not just strategists. They sit right in the middle.&lt;/p&gt;

&lt;p&gt;Think of them like a general contractor for a home renovation. The contractor doesn't just swing a hammer. They help you design the plan, pick the right materials, manage the work, and make sure the final result matches what you needed, not just what looked good on paper.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://www.mckinsey.com/capabilities/quantumblack/our-insights/the-state-of-ai" rel="noopener noreferrer"&gt;McKinsey's 2025 State of AI report&lt;/a&gt;, 78% of organizations now use AI in at least one business function. But only around 6% achieve significant, enterprise-wide results. That gap between "we're using AI" and "AI is actually helping our business" is exactly where AI consulting services come in.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Does an AI Consultant Actually Do Day-to-Day?
&lt;/h2&gt;

&lt;p&gt;Here is where most articles get vague. Let's break this down into real phases.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 1: The AI Opportunity Audit
&lt;/h3&gt;

&lt;p&gt;Before building anything, a good consultant spends time understanding your business.&lt;/p&gt;

&lt;p&gt;They look at your current workflows, your data, your tools, and your goals. They ask: Where are the bottlenecks? Where is time being wasted? Where could automation or prediction actually add value?&lt;/p&gt;

&lt;p&gt;This is not a one-hour meeting. It is typically a multi-week discovery process. It involves talking to department heads, reviewing data pipelines, and mapping out where AI can realistically help vs. where it would just add unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Many businesses skip this step and jump straight to building. That is one of the top reasons AI projects fail.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 2: Strategy and Roadmap Building
&lt;/h3&gt;

&lt;p&gt;Once they understand the business, a consultant builds a roadmap. This is a prioritized list of AI projects, ordered by value and feasibility.&lt;/p&gt;

&lt;p&gt;Not every AI idea is a good idea. A roadmap helps you focus on what will move the needle first, instead of chasing the flashiest use case.&lt;/p&gt;

&lt;p&gt;A solid roadmap answers these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What AI projects do we tackle first?&lt;/li&gt;
&lt;li&gt;What data do we need, and is it ready?&lt;/li&gt;
&lt;li&gt;How long will each project take?&lt;/li&gt;
&lt;li&gt;What does success actually look like?&lt;/li&gt;
&lt;li&gt;How does this fit into our existing tech stack?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Phase 3: Picking the Right Tools and Stack
&lt;/h3&gt;

&lt;p&gt;There are thousands of AI tools available in 2026. Large language models, vector databases, MLOps platforms, AutoML tools, fine-tuning services, the list keeps growing.&lt;/p&gt;

&lt;p&gt;A good consultant knows which ones are right for your specific problem, not which ones are trending this month.&lt;/p&gt;

&lt;p&gt;They look at your cloud provider (AWS, Azure, GCP), your data infrastructure, your team's existing skills, and your budget. Then they recommend a stack that actually fits — not the most expensive or the most popular.&lt;/p&gt;

&lt;p&gt;This step alone can save companies from expensive vendor lock-in or over-engineered solutions that nobody ends up using.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 4: Managing the Build and Deployment
&lt;/h3&gt;

&lt;p&gt;This is where the technical work happens. Depending on the team, a consultant might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lead a team of data engineers and ML engineers&lt;/li&gt;
&lt;li&gt;Review and approve model designs&lt;/li&gt;
&lt;li&gt;Oversee integration with production systems&lt;/li&gt;
&lt;li&gt;Set up monitoring and alerting for deployed models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are not just advising from the sidelines. They are in the work — reviewing code, unblocking issues, and making sure the solution is being built correctly.&lt;/p&gt;

&lt;p&gt;According to IDC, &lt;a href="https://opsiocloud.com/knowledge-base/what-is-ai-consultant-roles/" rel="noopener noreferrer"&gt;AI consulting demand grew 40% between 2024 and 2025&lt;/a&gt; — largely because companies realized they needed someone to manage this build process, not just hand over a strategy document and walk away.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 5: Governance, Ethics, and Ongoing Optimization
&lt;/h3&gt;

&lt;p&gt;After a model goes live, the work is not done.&lt;/p&gt;

&lt;p&gt;AI systems can drift over time. Their predictions get less accurate as the world changes. They can also produce biased or harmful outputs if they are not monitored carefully.&lt;/p&gt;

&lt;p&gt;A consultant puts governance frameworks in place: model monitoring, bias detection, data refresh schedules, and escalation paths for when something goes wrong. In regulated industries like finance, healthcare, or insurance, this step is not optional. It is legally required.&lt;/p&gt;

&lt;p&gt;Only 23% of IT leaders are confident their organizations can manage AI governance when rolling out generative AI tools, per a 2025 Gartner survey. This is a massive gap and it is one of the fastest-growing areas of demand in AI consulting right now.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Is an AI Consultant Different From a Data Scientist or Software Engineer?
&lt;/h2&gt;

&lt;p&gt;This question comes up a lot. Here is the simplest way to think about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Scientist:&lt;/strong&gt; Focused on building models. Highly technical. Not always thinking about business outcomes or how the model gets used in practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ML/Software Engineer:&lt;/strong&gt; Builds and deploys systems. Focused on code and infrastructure. Not always involved in strategy or stakeholder communication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Consultant:&lt;/strong&gt; Connects business goals to technical solutions. Manages the full lifecycle. Communicates across teams from the CEO to the dev team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A data scientist might build you a great churn prediction model. But a consultant makes sure it connects to your CRM, that the sales team knows how to use it, that it gets updated every quarter, and that someone is watching for problems when it drifts.&lt;/p&gt;

&lt;p&gt;Both roles are valuable. But they do very different jobs.&lt;/p&gt;
&lt;h2&gt;
  
  
  When Does a Business Actually Need AI Consulting Services?
&lt;/h2&gt;

&lt;p&gt;You probably need a consultant if any of these sound familiar:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1."We've been talking about AI for 18 months but haven't shipped anything."&lt;/strong&gt;&lt;br&gt;
You need someone to cut through the noise and create a real plan with clear milestones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2."We built something, but it's sitting unused."&lt;/strong&gt;&lt;br&gt;
You need help with change management, integration, and adoption — not just the model itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3."We don't know if our data is ready for AI."&lt;/strong&gt;&lt;br&gt;
A consultant will run a data readiness audit and tell you honestly what you have to work with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4."We're worried about AI doing something harmful or non-compliant."&lt;/strong&gt;&lt;br&gt;
You need governance expertise before you go live not after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. "Our team knows how to code, but doesn't know where to start."&lt;/strong&gt;&lt;br&gt;
Strategy always comes first. A clear roadmap from an experienced team can save months of wasted effort.&lt;/p&gt;

&lt;p&gt;For companies in retail, finance, healthcare, or any data-heavy industry, the gap between "wanting AI" and "using AI effectively" is often just a lack of structured guidance. Lucent Innovation's &lt;a href="https://www.lucentinnovation.com/services/ai-consulting" rel="noopener noreferrer"&gt;AI consulting experts&lt;/a&gt; work through exactly this process, from initial opportunity mapping all the way through to production deployment and ongoing governance.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Tools Do AI Consultants Actually Use in 2026?
&lt;/h2&gt;

&lt;p&gt;AI consultants are not just using ChatGPT. Here is a practical look at a real toolkit:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy &amp;amp; Discovery&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Miro or FigJam for workflow mapping&lt;/li&gt;
&lt;li&gt;Notion or Confluence for roadmap documentation&lt;/li&gt;
&lt;li&gt;Custom interview frameworks for stakeholder discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Data Readiness&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python (&lt;code&gt;pandas&lt;/code&gt;, &lt;code&gt;great_expectations&lt;/code&gt;) for data audits&lt;/li&gt;
&lt;li&gt;dbt for data transformation pipelines&lt;/li&gt;
&lt;li&gt;Databricks for large-scale data processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Model Development&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PyTorch or TensorFlow for custom model work&lt;/li&gt;
&lt;li&gt;Hugging Face for open-source LLM access&lt;/li&gt;
&lt;li&gt;OpenAI or Anthropic APIs for enterprise generative AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deployment &amp;amp; Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MLflow or Weights &amp;amp; Biases for experiment tracking&lt;/li&gt;
&lt;li&gt;Kubeflow or AWS SageMaker for production deployment&lt;/li&gt;
&lt;li&gt;Grafana or Datadog for monitoring and alerting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Governance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fairlearn or IBM AI Fairness 360 for bias detection&lt;/li&gt;
&lt;li&gt;AWS Macie or Microsoft Purview for data compliance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example of the kind of simple data readiness check a consultant might run before recommending any ML solution to a client. This is often the very first technical step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Basic AI Readiness Check — Data Quality Audit
# Run this before recommending any ML model to a client
# Gives a fast signal on whether the dataset is usable
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ai_readiness_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Quick data quality check before starting an AI project.
    Returns a readiness score and a list of key issues to fix.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

    &lt;span class="c1"&gt;# Check 1: Columns with &amp;gt;20% missing values
&lt;/span&gt;    &lt;span class="n"&gt;missing_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isnull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="n"&gt;high_missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;missing_pct&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;missing_pct&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;high_missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;High missing data in: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high_missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;

    &lt;span class="c1"&gt;# Check 2: Minimum row count for a usable ML dataset
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Low row count (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows). &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Most ML models need at least 1,000 rows to train reliably.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

    &lt;span class="c1"&gt;# Check 3: Too many duplicate rows
&lt;/span&gt;    &lt;span class="n"&gt;dup_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duplicated&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dup_pct&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dup_pct&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% duplicate rows found — clean before training.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;

    &lt;span class="c1"&gt;# Check 4: No numeric columns (most models need at least some)
&lt;/span&gt;    &lt;span class="n"&gt;numeric_cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_dtypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numeric_cols&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No numeric columns found. Data may need encoding first.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;readiness_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;issues_found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;columns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommendation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Good to proceed with model development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fix data quality issues before building any model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage:
# df = pd.read_csv("your_business_data.csv")
# result = ai_readiness_check(df)
# print(result)
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A lot of AI consulting begins exactly here with the data, not the model. Many companies believe they are "AI-ready" when their data tells a very different story. Running something like this before scoping a project saves weeks of rework.&lt;/p&gt;

&lt;p&gt;For businesses that need this kind of structured, end-to-end support from data readiness assessments all the way through model governance, Lucent Innovation's AI strategy and implementation consulting covers the full lifecycle across industries including retail, healthcare, and financial services.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>consulting</category>
      <category>machinelearning</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>How to Pick an AI Consulting Partner in 2026 Without Regret</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Fri, 05 Jun 2026 06:44:07 +0000</pubDate>
      <link>https://dev.to/lucy1/how-to-pick-an-ai-consulting-partner-in-2026-without-regret-18og</link>
      <guid>https://dev.to/lucy1/how-to-pick-an-ai-consulting-partner-in-2026-without-regret-18og</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; Hire an AI consulting partner the way you'd hire a senior engineer. Judge them on what they've shipped, not on the buzzwords in their deck. The good ones say no to bad-fit projects, put working code in front of you early, and tell you straight where AI won't help. The rest is theater.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fair warning before we get going:&lt;/strong&gt; I run an AI and data shop, so I've got skin in this. I'll flag it where it matters. This isn't a pitch, though  it's the checklist I wish more founders used. Bad engagements are exactly what make this whole field smell like snake oil, and I'm tired of cleaning up after them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does picking wrong hurt so much?
&lt;/h2&gt;

&lt;p&gt;It's not the invoice. It's the quarter you burn, the engineers who quietly stop believing "AI" means anything real, and the brittle demo that folds the second production data touches it.&lt;/p&gt;

&lt;p&gt;And the opportunity cost is brutal. While you were untangling someone's over-engineered RAG pipeline, a competitor shipped something boring that just worked. Speed-to-learning beats sophistication here almost every time, and the wrong partner optimizes for the wrong one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should I actually look for?
&lt;/h2&gt;

&lt;p&gt;Skip the logo wall. When you're weighing AI consulting services, here's what actually tells you something:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy16dyy9m5wctqhyfcx4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy16dyy9m5wctqhyfcx4x.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shipped systems, not slides.&lt;/strong&gt; Ask to see something running. Real work leaves a trail — repos, dashboards, eval numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated scoping.&lt;/strong&gt; A good partner tells you which 80% of your idea to cut for v1. Say-yes-to-everything means they're selling hours, not outcomes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data honesty.&lt;/strong&gt; The first hard question should be about your data: where it lives, how messy it is, who owns it. Nobody asks? Walk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An exit ramp.&lt;/strong&gt; You want to own the code, the model choices, the docs. Anyone building you a black box only they can maintain is building themselves a job not solving your problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the thing about good AI strategy consulting: it starts from your business constraint, not from a model. If the opening call is about which LLM to pick rather than which decision you're trying to improve, that's a yellow flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I avoid getting burned?
&lt;/h2&gt;

&lt;p&gt;Three moves that have saved me and people I trust.&lt;br&gt;
Run a paid pilot first. Two to four weeks, tight scope, one real deliverable. And pay for it — free pilots pull the wrong incentives on both sides. You'll learn more in one honest sprint than in five sales calls.&lt;/p&gt;

&lt;p&gt;Then ask for a reference who had a project go sideways. Anyone can hand you a happy logo. The question that actually works is, "Tell me about an engagement that didn't go to plan." How they answer tells you how they'll treat you when something breaks. Because something will.&lt;/p&gt;

&lt;p&gt;And make them explain their evals. If they can't tell you how they'll measure whether the thing works accuracy, latency, cost per call, hallucination rate they're guessing. Guessing is fine at a hackathon. Not on your budget.&lt;/p&gt;

&lt;p&gt;Teams that work this way are happy to scope a small, honest pilot before asking for the big commitment. For transparency, that's roughly how our own AI consulting practice runs but honestly, the principle matters more than the vendor. Hold whoever you're evaluating to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build, buy, or partner at all?
&lt;/h2&gt;

&lt;p&gt;Not every problem needs a consultant. Strong ML engineers and a clear use case? Build it. A SaaS tool already covers 90%? Buy that. Partnering earns its keep when the problem is real, the stakes are high, and you need to move faster than hiring allows or when you want your own engineers learning next to people who've done it before.&lt;/p&gt;

&lt;p&gt;If you do bring someone in, treat them like a teammate with an expiry date. Your team should be sharper when they leave, not more dependent. The right &lt;a href="https://www.lucentinnovation.com/services/ai-consulting" rel="noopener noreferrer"&gt;AI strategy consulting&lt;/a&gt; engagement hands over knowledge on the way out. That's the whole difference between a partner and a crutch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Judge partners on shipped work, sharp scoping, and data honesty. De-risk with a short paid pilot and a brutal reference check. Insist on owning what gets built. The best one leaves your team stronger — and then leaves.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>startup</category>
      <category>aiconsultingpartner</category>
      <category>aiconsulting</category>
    </item>
    <item>
      <title>Batch vs Streaming Pipelines: How I Actually Choose Between Them</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Fri, 05 Jun 2026 06:43:12 +0000</pubDate>
      <link>https://dev.to/lucy1/batch-vs-streaming-pipelines-how-i-actually-choose-between-them-4fdn</link>
      <guid>https://dev.to/lucy1/batch-vs-streaming-pipelines-how-i-actually-choose-between-them-4fdn</guid>
      <description>&lt;p&gt;Every data pipeline starts with one big question before a single line of code gets written.&lt;/p&gt;

&lt;p&gt;Should I process data in scheduled chunks? Or should I process it the moment events arrive?&lt;/p&gt;

&lt;p&gt;That is the batch vs streaming decision. It sounds simple. But in real projects, it shapes everything: which tools you pick, how much you spend each month, what guarantees you can make about fresh data, and how many nights you spend fixing production incidents.&lt;/p&gt;

&lt;p&gt;I have seen teams pick streaming when batch would have worked just fine. I have also seen the opposite. Both mistakes are expensive. This post walks through how I think about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Batch Processing Actually Means
&lt;/h2&gt;

&lt;p&gt;Batch processing collects data over a time window and then processes it all at once when a scheduled trigger fires.&lt;/p&gt;

&lt;p&gt;Think about doing laundry. You do not wash one shirt the moment it gets dirty. You wait until you have a full load, then run the machine. The shirts pile up during the week. On Sunday, the machine runs.&lt;/p&gt;

&lt;p&gt;Data batch pipelines work the same way. Source data builds up in a staging area. At a set time, usually overnight or hourly, a job picks up everything that arrived, runs the transformations, and loads the results into the destination.&lt;/p&gt;

&lt;p&gt;The batch job has a clear start. It has a clear end. When it finishes, the destination has a snapshot of data as of the run time. Between runs, nothing changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What batch is great at:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Batch handles complex transformations well because there is zero time pressure per record. A batch job can join across tables with hundreds of millions of rows. It can run expensive multi-level calculations. It can apply feature engineering for machine learning without worrying about processing each event in milliseconds.&lt;/p&gt;

&lt;p&gt;Batch pipelines are also much easier to test, debug, and rerun. When a transformation gives wrong results, you fix the logic and reprocess the affected time window. The worst thing that happens is a delayed job, not a production fire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where batch falls short:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Batch produces stale data. How stale depends on the schedule. Nightly jobs produce data up to 24 hours old. Hourly jobs produce data up to 60 minutes old.&lt;/p&gt;

&lt;p&gt;For use cases where decisions depend on what is happening right now, that staleness is a real problem.&lt;/p&gt;

&lt;p&gt;A fraud detection system that runs on a nightly batch schedule is not a fraud detection system. It is a fraud reporting system. The fraud already happened hours ago.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Streaming Processing Actually Means
&lt;/h2&gt;

&lt;p&gt;Streaming treats data as a continuous flow of individual events. Each event gets processed the moment it arrives, without waiting for others to pile up first.&lt;/p&gt;

&lt;p&gt;Think about a moving walkway at an airport. People step onto the walkway as they arrive. Each person moves forward right away. Nobody waits for 500 people to gather before the walkway starts moving. The walkway runs all day whether one person is on it or ten thousand.&lt;/p&gt;

&lt;p&gt;A streaming pipeline works the same way. An event source like Apache Kafka, Amazon Kinesis, or Google Pub/Sub delivers events in real time. The stream processor picks up each event, applies the transformation logic, and writes the result downstream within milliseconds to seconds. The pipeline runs 24 hours a day, seven days a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What streaming is great at:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Streaming is right when the output of the pipeline needs to trigger an action or update a system in real time.&lt;/p&gt;

&lt;p&gt;Fraud detection needs to check whether a transaction looks suspicious before approving it. That decision cannot wait 60 minutes for the next batch run.&lt;/p&gt;

&lt;p&gt;An e-commerce recommendation engine that adapts to clicks, cart additions, and browsing behavior as they happen gives a fundamentally different experience than one running on overnight batch data.&lt;/p&gt;

&lt;p&gt;Infrastructure health dashboards that catch CPU spikes, error rate increases, or latency anomalies need second-level data, not hourly summaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where streaming falls short:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Streaming infrastructure is a lot more complex to run than batch.&lt;/p&gt;

&lt;p&gt;Stream processing introduces distributed processing requirements, state management, and fault tolerance mechanisms that batch engineers rarely deal with. Systems consume compute resources at all times rather than only during defined job windows.&lt;/p&gt;

&lt;p&gt;Two failure modes in streaming catch teams off guard. The first is backpressure: incoming events exceed processing capacity, lag builds up, and outputs start describing events from minutes ago instead of seconds ago.&lt;/p&gt;

&lt;p&gt;The second is silent correctness drift. Streaming systems often keep running even when data quality issues occur. Duplicate events, missing events, or schema changes can slowly corrupt outputs while dashboards still show active data.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Comparison at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Batch&lt;/th&gt;
&lt;th&gt;Streaming&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How data moves&lt;/td&gt;
&lt;td&gt;Collects over time, processes in one run&lt;/td&gt;
&lt;td&gt;Each event processed the moment it arrives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Minutes to hours&lt;/td&gt;
&lt;td&gt;Milliseconds to seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Compute spins up for the job, shuts down after&lt;/td&gt;
&lt;td&gt;Always on, always running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Lower baseline, pay only when jobs run&lt;/td&gt;
&lt;td&gt;Higher baseline, persistent infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;Lower, simpler error handling&lt;/td&gt;
&lt;td&gt;Higher, state management and fault tolerance required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure mode&lt;/td&gt;
&lt;td&gt;Delayed job, rerun and recover&lt;/td&gt;
&lt;td&gt;Production incident, live intervention needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging&lt;/td&gt;
&lt;td&gt;Rerun the job on the failed time window&lt;/td&gt;
&lt;td&gt;Replay events from the message queue checkpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema change&lt;/td&gt;
&lt;td&gt;Pipeline breaks loudly on next run&lt;/td&gt;
&lt;td&gt;Can cause silent issues if not monitored&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The One Question That Decides It
&lt;/h2&gt;

&lt;p&gt;One question cuts through most of the debate: &lt;strong&gt;what happens if the data is one hour old?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is nothing meaningful, batch is probably the right choice.&lt;/p&gt;

&lt;p&gt;If the answer is a real business loss, streaming earns its complexity.&lt;/p&gt;

&lt;p&gt;Streaming is justified when the output triggers action. If the output only feeds retrospective analysis, batch is usually sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four Questions to Ask Before Picking
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. How fresh does the data need to be to be useful?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most analytics use cases tolerate data that is a few hours old. A weekly revenue report does not need second-level freshness. A fraud detection engine does. Know the actual freshness requirement before assuming you need streaming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Does stale data cause a real business loss?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a customer gets a product recommendation based on yesterday's browsing instead of what they clicked five minutes ago, does that cost the business money? If yes, streaming may be worth it. If it is a marginal difference, batch is almost certainly the right choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What is the operational capacity of your team?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Streaming infrastructure needs engineers who understand state management, checkpointing, exactly-once delivery semantics, and how to respond to backpressure incidents at midnight. If your team is small or your use case does not demand real-time results, that complexity is cost without benefit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Is real-time the actual requirement, or is faster batch enough?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stakeholders often say they want real-time when what they mean is they want data more current than nightly. A pipeline that runs every 15 minutes often satisfies that requirement at a fraction of the cost and complexity of a true streaming system.&lt;/p&gt;

&lt;p&gt;When stakeholders say "real-time" but would accept hourly updates without meaningful business impact, they want faster batch, not streaming.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Use Cases: When Each Pattern Wins
&lt;/h2&gt;

&lt;h3&gt;
  
  
  When Batch Is the Right Answer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Nightly financial reporting.&lt;/strong&gt; A bank's end-of-day ledger reconciliation processes every transaction from the day against regulatory limits and account balances. The job needs to run across the full day's dataset, apply complex multi-table joins, and produce a validated snapshot. Batch runs at end of day. Streaming adds nothing here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ML model training.&lt;/strong&gt; Training a machine learning model requires a large, static dataset processed multiple times. Streaming the training data adds enormous complexity without improving model quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Large-scale historical ETL.&lt;/strong&gt; Migrating three years of transactional data into a new warehouse schema is a batch workload. The data already exists. There is no real-time requirement. Batch processes it once and moves on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance reporting.&lt;/strong&gt; Monthly, quarterly, or annual regulatory reports that pull and aggregate data across long time windows are batch workloads. The business cost of a slightly delayed report is low. The complexity of a streaming system is not justified.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Streaming Is the Right Answer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fraud detection.&lt;/strong&gt; Payment authorization systems need to evaluate whether a transaction is fraudulent before it clears, typically in under 500 milliseconds. A batch pipeline running every 30 minutes would approve or deny transactions without the context of what happened in the last 30 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-time feature serving for ML inference.&lt;/strong&gt; When a deployed ML model needs features computed from recent user behavior to make a prediction, streaming pipelines update the feature store in real time. A recommendation model running on features from last night's batch is operating blind to today's context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live operational dashboards.&lt;/strong&gt; A supply chain control tower showing current inventory levels, in-transit shipments, and order status across hundreds of warehouses needs second-level freshness. An overnight batch job cannot surface a stockout until the next morning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IoT and sensor telemetry.&lt;/strong&gt; In manufacturing, logistics, and energy, IoT devices generate continuous streams of sensor data that batch pipelines were not built to ingest or process. Predictive maintenance models that detect equipment issues before failure require streaming ingestion of live sensor data.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Middle Ground Teams Often Miss: Micro-Batch
&lt;/h2&gt;

&lt;p&gt;Between batch and streaming sits micro-batch processing. It is the pattern that Apache Spark Structured Streaming uses by default, and it solves most "near real-time" requirements without the full complexity of continuous streaming.&lt;/p&gt;

&lt;p&gt;Micro-batch runs the same pipeline logic as streaming but on a very short fixed interval: every 30 seconds, every minute, every 5 minutes. Data builds up for the interval, then the batch processes it. Latency is measured in seconds to low minutes rather than hours.&lt;/p&gt;

&lt;p&gt;Most use cases that stakeholders describe as "real-time" actually tolerate micro-batch latency. A dashboard that refreshes every minute looks real-time to every user. A data freshness requirement of "under 5 minutes" is achievable with micro-batch at a fraction of the streaming infrastructure cost.&lt;/p&gt;

&lt;p&gt;Here is how the decision tree actually looks in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hours of latency are fine: standard batch on a schedule&lt;/li&gt;
&lt;li&gt;Minutes of latency are fine: micro-batch with short trigger intervals&lt;/li&gt;
&lt;li&gt;Sub-minute latency is required and the output triggers action: true streaming with Spark Structured Streaming&lt;/li&gt;
&lt;li&gt;Sub-second latency is required: Real-Time Mode on Databricks Spark Structured Streaming&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Real Cost of Streaming: What Teams Underestimate
&lt;/h2&gt;

&lt;p&gt;A simple batch ETL pipeline costs between $15,000 and $50,000 to build. A production streaming pipeline with proper monitoring costs between $50,000 and $200,000 or more. That is a 4x to 10x difference at the build stage alone.&lt;/p&gt;

&lt;p&gt;Operational cost compounds on top of that. Streaming systems need always-on compute, persistent state storage, continuous monitoring for lag and backpressure, and engineers who can respond to incidents at any hour.&lt;/p&gt;

&lt;p&gt;Three costs teams consistently underestimate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State management.&lt;/strong&gt; Streaming pipelines that compute windowed aggregations, sessionization, or joins across event streams must maintain state across every event. State grows with data volume. Managing state storage, checkpointing, and cleanup is a continuous engineering concern with no equivalent in batch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exactly-once delivery.&lt;/strong&gt; Guaranteeing that each event is processed exactly once, not duplicated or dropped, requires careful coordination between the message queue, the stream processor, and the output destination. Getting this wrong means silent duplicate records or missing events in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema evolution.&lt;/strong&gt; When a source system changes its event schema, a batch pipeline fails loudly on the next scheduled run. A streaming pipeline may silently accept the new schema, produce corrupt output, and keep running for days before anyone notices.&lt;/p&gt;

&lt;p&gt;None of this means streaming is wrong. It means streaming should be chosen when the use case justifies the cost, not because it sounds more modern than batch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lambda vs Kappa: Two Ways to Run Both at Once
&lt;/h2&gt;

&lt;p&gt;Many production systems need both patterns. Two architectural approaches define how teams organize that combination.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda Architecture
&lt;/h3&gt;

&lt;p&gt;Lambda runs two parallel pipelines. A batch layer reprocesses the full historical dataset on a schedule and produces accurate, complete results. A speed layer processes real-time events and produces approximate but current results. A serving layer merges outputs from both and delivers whichever is more current and accurate.&lt;/p&gt;

&lt;p&gt;The batch layer produces trusted, complete data. The speed layer fills in the gap between now and the last batch run. When the batch layer catches up, it overrides the speed layer's approximate output.&lt;/p&gt;

&lt;p&gt;Lambda works well when accuracy matters for historical data but approximate freshness is acceptable for recent data. The real cost is operational: two separate pipelines to build, test, and maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kappa Architecture
&lt;/h3&gt;

&lt;p&gt;Kappa replaces the dual-pipeline design with a single streaming pipeline that handles everything. All data, historical and real-time, flows through the same stream processor.&lt;/p&gt;

&lt;p&gt;Historical reprocessing works by replaying events from a durable message queue like Apache Kafka, which retains events for a configurable window. To reprocess, you replay from the beginning of the queue through the same pipeline code. No separate batch layer needed.&lt;/p&gt;

&lt;p&gt;Kappa is simpler to maintain but requires your message queue to retain data long enough to support replays. It also requires that your transformation logic works correctly as a streaming pipeline, which rules out certain types of complex, multi-pass batch transformations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference: Which Pattern for Which Use Case
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nightly revenue reporting&lt;/td&gt;
&lt;td&gt;Batch&lt;/td&gt;
&lt;td&gt;Data freshness within hours is fine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML model training&lt;/td&gt;
&lt;td&gt;Batch&lt;/td&gt;
&lt;td&gt;Requires full static dataset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Historical data migration&lt;/td&gt;
&lt;td&gt;Batch&lt;/td&gt;
&lt;td&gt;Data already exists, no real-time constraint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fraud detection&lt;/td&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;Decision must happen before transaction clears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time ML feature serving&lt;/td&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;Model inference needs current behavioral context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IoT anomaly detection&lt;/td&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;Equipment failure cannot wait for next batch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live inventory dashboards&lt;/td&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;Stockout response needs current state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly compliance reports&lt;/td&gt;
&lt;td&gt;Batch&lt;/td&gt;
&lt;td&gt;Fixed window, no freshness urgency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  My Rule of Thumb
&lt;/h2&gt;

&lt;p&gt;Before you write a line of code, ask: does the output of this pipeline trigger an action, or does it inform analysis?&lt;/p&gt;

&lt;p&gt;If it triggers an action and that action loses value after a few minutes, build streaming.&lt;/p&gt;

&lt;p&gt;If it informs analysis and the insights hold up for a few hours, build batch.&lt;/p&gt;

&lt;p&gt;And if your stakeholders say "real-time" but can actually accept updates every few minutes, build micro-batch. It gives you most of the freshness at a fraction of the cost.&lt;/p&gt;

&lt;p&gt;The goal is not to use the most impressive technology. The goal is to ship the simplest system that meets the actual latency requirement and does not wake anyone up at 3 AM.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is part of a series on modern data engineering. For more on how these patterns connect to ETL vs ELT design choices, how Databricks handles both batch and streaming in one platform, and how to design for schema evolution at scale, check out the &lt;a href="https://www.lucentinnovation.com/resources/it-insights/modern-data-engineering-guide" rel="noopener noreferrer"&gt;Modern Data Engineering Guide&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Set Up Local Data Engineering Environments with Docker Compose</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Thu, 28 May 2026 09:17:33 +0000</pubDate>
      <link>https://dev.to/lucy1/how-to-set-up-local-data-engineering-environments-with-docker-compose-310j</link>
      <guid>https://dev.to/lucy1/how-to-set-up-local-data-engineering-environments-with-docker-compose-310j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Docker Compose lets you spin up a full local data stack — Airflow, PostgreSQL, Spark, Redis — with a single YAML file and one command. This guide walks you through the exact setup, real compose configs, and the mistakes most engineers make along the way.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Your Local Data Environment Is Probably a Mess
&lt;/h2&gt;

&lt;p&gt;Here's the thing: most data engineers I know have a local setup that technically works — but only on their machine, on a good day, when the stars align.&lt;/p&gt;

&lt;p&gt;You install PostgreSQL manually. Pin a Python version. Struggle to get Airflow running without breaking something else. And then a new teammate joins and spends three days just trying to reproduce your environment.&lt;/p&gt;

&lt;p&gt;That's not an engineering problem. It's a tooling problem. And Docker Compose solves it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndz16rnptrusxh7yto2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndz16rnptrusxh7yto2o.png" alt="Manual installs vs Docker Compose workflow" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker Compose lets you describe your entire local data stack as code — services, networks, volumes, environment variables — and spin it up or tear it down with one command. No more "works on my machine." No more three-day onboarding nightmares.&lt;/p&gt;

&lt;p&gt;This guide covers the full picture: what Docker Compose actually is (and what it's not), the building blocks you need to understand, and a production-quality example stack with Airflow, PostgreSQL, Redis, and Spark.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Docker Compose (and What It's Not)
&lt;/h2&gt;

&lt;p&gt;Docker Compose is an orchestration tool for defining and running &lt;strong&gt;multi-container Docker applications&lt;/strong&gt; on a single machine. You write a &lt;code&gt;compose.yaml&lt;/code&gt; file that describes each service — what image it uses, what ports it exposes, how it connects to other services, and where it stores data.&lt;/p&gt;

&lt;p&gt;A quick note before we go further: &lt;strong&gt;Docker Compose v1 reached end-of-life in July 2023.&lt;/strong&gt; The old &lt;code&gt;docker-compose&lt;/code&gt; binary (with the hyphen) is gone. You should be using Docker Compose v2, which ships as a built-in CLI plugin. If you see &lt;code&gt;docker-compose&lt;/code&gt; anywhere in your scripts or tutorials, replace it with &lt;code&gt;docker compose&lt;/code&gt; (space, no hyphen).&lt;/p&gt;

&lt;p&gt;Also worth knowing: the &lt;code&gt;version:&lt;/code&gt; field at the top of your compose file is now officially deprecated. You don't need it. Drop it entirely from any new file you write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Compose is NOT:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A replacement for Kubernetes in production&lt;/li&gt;
&lt;li&gt;A tool for managing distributed multi-machine deployments&lt;/li&gt;
&lt;li&gt;A substitute for proper secrets management in prod&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for local development, CI pipelines, and single-machine staging? It's hard to beat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt; Docker official documentation — &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;docs.docker.com/compose&lt;/a&gt;; freeCodeCamp Docker Compose v2 guide (2026); Docker Compose specification at &lt;a href="https://compose-spec.io" rel="noopener noreferrer"&gt;compose-spec.io&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we write a single line of YAML, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker Desktop&lt;/strong&gt; (v4.0+) or &lt;strong&gt;Docker Engine + docker-compose-plugin&lt;/strong&gt; on Linux&lt;/li&gt;
&lt;li&gt;At least &lt;strong&gt;8GB RAM&lt;/strong&gt; available (data stacks eat memory)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 CPU cores&lt;/strong&gt; minimum — Spark in particular needs headroom&lt;/li&gt;
&lt;li&gt;Basic familiarity with the command line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run this to confirm your setup is current:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose version
&lt;span class="c"&gt;# Should show v2.24 or later in 2026&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that command fails or shows v1.x, update Docker before continuing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the Core Building Blocks
&lt;/h2&gt;

&lt;p&gt;Before jumping into the full stack, you need a mental model of the four things Docker Compose actually manages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5r3mf70kci518kuwl3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5r3mf70kci518kuwl3x.png" alt="Docker Compose core concepts infographic" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Services
&lt;/h3&gt;

&lt;p&gt;A service is a running container. Each entry under &lt;code&gt;services:&lt;/code&gt; in your compose file becomes one or more containers. For a data engineering stack, your services are things like your database, your orchestrator, your message broker, your transformation tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Networks
&lt;/h3&gt;

&lt;p&gt;By default, every service in a compose file can talk to every other service using the &lt;strong&gt;service name&lt;/strong&gt; as the hostname. No IP addresses. No manual DNS. This is one of the most underrated features — your Airflow scheduler connects to Postgres by literally using &lt;code&gt;postgres&lt;/code&gt; as the hostname.&lt;/p&gt;

&lt;h3&gt;
  
  
  Volumes
&lt;/h3&gt;

&lt;p&gt;Volumes are how your data survives container restarts. There are two flavors: &lt;strong&gt;named volumes&lt;/strong&gt; (managed by Docker, recommended for databases) and &lt;strong&gt;bind mounts&lt;/strong&gt; (a folder on your host machine mounted into the container, useful for DAGs, scripts, and code you're actively editing).&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment Variables
&lt;/h3&gt;

&lt;p&gt;Never hardcode credentials in your compose file. Always use a &lt;code&gt;.env&lt;/code&gt; file and reference variables with &lt;code&gt;${VARIABLE_NAME}&lt;/code&gt; syntax. Your &lt;code&gt;.env&lt;/code&gt; file stays out of version control. Your &lt;code&gt;compose.yaml&lt;/code&gt; doesn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the Stack: A Real Data Engineering Environment
&lt;/h2&gt;

&lt;p&gt;Let's build something real. This stack covers the tools that appear in most data engineering workflows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Metadata DB + data warehouse&lt;/td&gt;
&lt;td&gt;5432&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Message broker for Celery tasks&lt;/td&gt;
&lt;td&gt;6379&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apache Airflow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Workflow orchestration&lt;/td&gt;
&lt;td&gt;8080&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apache Spark&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Distributed data processing&lt;/td&gt;
&lt;td&gt;4040, 7077&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adminer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lightweight DB GUI&lt;/td&gt;
&lt;td&gt;8085&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8srnf5fsbp71vuom9ps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8srnf5fsbp71vuom9ps.png" alt="Data engineering stack and flowchart" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Project Structure
&lt;/h3&gt;

&lt;p&gt;Start with a clean folder structure. This matters more than most tutorials admit — messy folders create tangled volume mounts and confusing build contexts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data-eng-local/
├── compose.yaml
├── .env
├── .env.example
├── airflow/
│   ├── dags/
│   ├── logs/
│   ├── plugins/
│   └── config/
├── spark/
│   └── jobs/
├── postgres/
│   └── init/
│       └── 01_create_schemas.sql
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two rules: put your &lt;code&gt;compose.yaml&lt;/code&gt; at the root, and never commit &lt;code&gt;.env&lt;/code&gt; to git. Add it to &lt;code&gt;.gitignore&lt;/code&gt; now, before you forget.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — The &lt;code&gt;.env&lt;/code&gt; File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env — DO NOT commit to version control
POSTGRES_USER=dataeng
POSTGRES_PASSWORD=changeme_local
POSTGRES_DB=warehouse

AIRFLOW_UID=50000
AIRFLOW__CORE__FERNET_KEY=your_fernet_key_here
AIRFLOW__WEBSERVER__SECRET_KEY=your_secret_key_here

REDIS_PASSWORD=redis_local_pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a Fernet key with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3 — The &lt;code&gt;compose.yaml&lt;/code&gt; File
&lt;/h3&gt;

&lt;p&gt;Here's the full configuration. Read the inline comments — they explain the decisions, not just the syntax.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# compose.yaml — No version field needed (deprecated in Compose v2)&lt;/span&gt;

&lt;span class="na"&gt;x-airflow-common&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;airflow-common&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache/airflow:3.0.4&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;airflow-common-env&lt;/span&gt;
    &lt;span class="na"&gt;AIRFLOW__CORE__EXECUTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CeleryExecutor&lt;/span&gt;
    &lt;span class="na"&gt;AIRFLOW__DATABASE__SQL_ALCHEMY_CONN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql+psycopg2://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres/${POSTGRES_DB}&lt;/span&gt;
    &lt;span class="na"&gt;AIRFLOW__CELERY__RESULT_BACKEND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db+postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres/${POSTGRES_DB}&lt;/span&gt;
    &lt;span class="na"&gt;AIRFLOW__CELERY__BROKER_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://:${REDIS_PASSWORD}@redis:6379/0&lt;/span&gt;
    &lt;span class="na"&gt;AIRFLOW__CORE__FERNET_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${AIRFLOW__CORE__FERNET_KEY}&lt;/span&gt;
    &lt;span class="na"&gt;AIRFLOW__WEBSERVER__SECRET_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${AIRFLOW__WEBSERVER__SECRET_KEY}&lt;/span&gt;
    &lt;span class="na"&gt;AIRFLOW_UID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${AIRFLOW_UID}&lt;/span&gt;
  &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./airflow/dags:/opt/airflow/dags&lt;/span&gt;        &lt;span class="c1"&gt;# Bind mount — edit DAGs without rebuilding&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./airflow/logs:/opt/airflow/logs&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./airflow/plugins:/opt/airflow/plugins&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./airflow/config:/opt/airflow/config&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;              &lt;span class="c1"&gt;# Wait for postgres to be ready — not just started&lt;/span&gt;
    &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ─── DATABASE ──────────────────────────────────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${POSTGRES_USER}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${POSTGRES_PASSWORD}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${POSTGRES_DB}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;          &lt;span class="c1"&gt;# Named volume — data survives restarts&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./postgres/init:/docker-entrypoint-initdb.d&lt;/span&gt;     &lt;span class="c1"&gt;# SQL files run on first startup&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${POSTGRES_USER}"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="c1"&gt;# ─── MESSAGE BROKER ────────────────────────────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;                    &lt;span class="c1"&gt;# Alpine = smaller image, same functionality&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-server --requirepass ${REDIS_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379:6379"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_data:/data&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-a"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${REDIS_PASSWORD}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="c1"&gt;# ─── AIRFLOW ───────────────────────────────────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;airflow-init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*airflow-common&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;airflow db migrate &amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="s"&gt;airflow users create \&lt;/span&gt;
          &lt;span class="s"&gt;--username admin \&lt;/span&gt;
          &lt;span class="s"&gt;--password admin \&lt;/span&gt;
          &lt;span class="s"&gt;--firstname Admin \&lt;/span&gt;
          &lt;span class="s"&gt;--lastname User \&lt;/span&gt;
          &lt;span class="s"&gt;--role Admin \&lt;/span&gt;
          &lt;span class="s"&gt;--email admin@example.com&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;                            &lt;span class="c1"&gt;# Run once and exit — not a long-running service&lt;/span&gt;

  &lt;span class="na"&gt;airflow-webserver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*airflow-common&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webserver&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--fail"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8080/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;airflow-scheduler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*airflow-common&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scheduler&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;airflow-worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*airflow-common&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;celery worker&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="c1"&gt;# ─── SPARK ─────────────────────────────────────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;spark-master&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitnami/spark:3.5&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SPARK_MODE=master&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4040:4040"&lt;/span&gt;                          &lt;span class="c1"&gt;# Spark UI&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;7077:7077"&lt;/span&gt;                          &lt;span class="c1"&gt;# Spark master port&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./spark/jobs:/opt/spark-jobs&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;spark-worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitnami/spark:3.5&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SPARK_MODE=worker&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SPARK_MASTER_URL=spark://spark-master:7077&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SPARK_WORKER_MEMORY=2G&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SPARK_WORKER_CORES=2&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;spark-master&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="c1"&gt;# ─── DB GUI ────────────────────────────────────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;adminer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adminer:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8085:8080"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

&lt;span class="c1"&gt;# ─── VOLUMES ───────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="c1"&gt;# Docker-managed, persists across down/up cycles&lt;/span&gt;
  &lt;span class="na"&gt;redis_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4 — Start the Stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# First time — initialize Airflow's database and create the admin user&lt;/span&gt;
docker compose up airflow-init

&lt;span class="c"&gt;# Then start everything else in detached mode&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Watch it come up&lt;/span&gt;
docker compose ps

&lt;span class="c"&gt;# Stream logs for a specific service (useful for debugging)&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; airflow-scheduler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Once everything is green, your services are at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Airflow UI:&lt;/strong&gt; &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; (admin / admin)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spark UI:&lt;/strong&gt; &lt;a href="http://localhost:4040" rel="noopener noreferrer"&gt;http://localhost:4040&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adminer:&lt;/strong&gt; &lt;a href="http://localhost:8085" rel="noopener noreferrer"&gt;http://localhost:8085&lt;/a&gt; (server: &lt;code&gt;postgres&lt;/code&gt;, user: &lt;code&gt;dataeng&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Health Checks: The Feature Most People Skip
&lt;/h2&gt;

&lt;p&gt;This is the part most tutorials skip — and it's genuinely important.&lt;/p&gt;

&lt;p&gt;Without health checks, &lt;code&gt;depends_on&lt;/code&gt; is almost useless. By default, Docker considers a container "started" the moment the process launches, not when the service inside it is actually ready to accept connections. PostgreSQL needs a few seconds to initialize. Redis needs a moment to come up. If Airflow tries to connect before they're ready, it crashes — and you end up with a confusing pile of restart loops.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;condition: service_healthy&lt;/code&gt; syntax in &lt;code&gt;depends_on&lt;/code&gt; fixes this. It tells Docker: don't start this service until that other service's health check is passing. Pair it with a proper &lt;code&gt;healthcheck&lt;/code&gt; block on the dependency, and your stack starts in the right order every time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This is how you do it properly&lt;/span&gt;
&lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;  &lt;span class="c1"&gt;# ← This is the key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, you're relying on timing. Timing is not a strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Mistakes and How to Avoid Them
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hardcoding credentials in compose.yaml&lt;/strong&gt; — Don't. Use &lt;code&gt;.env&lt;/code&gt; files. It takes 30 seconds to set up and prevents you from accidentally committing passwords to a public repo. It happens more than you'd think.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;docker-compose&lt;/code&gt; (v1) instead of &lt;code&gt;docker compose&lt;/code&gt; (v2)&lt;/strong&gt; — The old binary is dead. If you're copying configs from tutorials older than mid-2023, check for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Including &lt;code&gt;version:&lt;/code&gt; in your compose file&lt;/strong&gt; — This field is obsolete as of Docker Compose v2 and now triggers deprecation warnings in Docker Desktop. Remove it from any file you write or maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not pinning image versions&lt;/strong&gt; — &lt;code&gt;postgres:latest&lt;/code&gt; is a trap. One upgrade and your init SQL might fail, your connection string might change, your extension might not exist. Pin to &lt;code&gt;postgres:16&lt;/code&gt;. Always.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting to add &lt;code&gt;.env&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;&lt;/strong&gt; — Seriously. Do this first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running &lt;code&gt;docker compose down -v&lt;/code&gt;&lt;/strong&gt; when you meant &lt;code&gt;docker compose down&lt;/code&gt; — The &lt;code&gt;-v&lt;/code&gt; flag deletes named volumes. That means your database data is gone. There's no undo. Be very intentional with that flag.&lt;/p&gt;




&lt;h2&gt;
  
  
  Working With Your Stack Day-to-Day
&lt;/h2&gt;

&lt;p&gt;Once it's running, these are the commands you'll reach for most:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check what's running and the health status&lt;/span&gt;
docker compose ps

&lt;span class="c"&gt;# Tail logs from everything&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Tail logs from one service only&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; airflow-worker

&lt;span class="c"&gt;# Restart a single service without touching the rest&lt;/span&gt;
docker compose restart airflow-scheduler

&lt;span class="c"&gt;# Run a one-off command inside a running container&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;postgres psql &lt;span class="nt"&gt;-U&lt;/span&gt; dataeng &lt;span class="nt"&gt;-d&lt;/span&gt; warehouse

&lt;span class="c"&gt;# Open a shell in a container for debugging&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;airflow-webserver bash

&lt;span class="c"&gt;# Stop everything (preserves volumes — safe)&lt;/span&gt;
docker compose down

&lt;span class="c"&gt;# Nuclear option — stops everything AND deletes all volumes&lt;/span&gt;
docker compose down &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjzfradhzoae61o8pr2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjzfradhzoae61o8pr2v.png" alt="Docker Compose developer workflow diagram" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Managing Multiple Environments with Profiles
&lt;/h2&gt;

&lt;p&gt;Here's something worth knowing once your stack gets more complex: Docker Compose Profiles let you define services that only start in certain contexts. You tag a service with &lt;code&gt;profiles: [dev]&lt;/code&gt; or &lt;code&gt;profiles: [monitoring]&lt;/code&gt; and it only runs when you explicitly request that profile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# This only starts when you run: docker compose --profile monitoring up&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:latest&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how you keep a single &lt;code&gt;compose.yaml&lt;/code&gt; that works for all environments — local dev, CI, staging — without maintaining multiple files. It's one of the features that quietly makes Compose much more production-capable than its reputation suggests.&lt;/p&gt;




&lt;h2&gt;
  
  
  E-E-A-T Reference: Tools, Versions, and Sources
&lt;/h2&gt;

&lt;p&gt;Here's a quick reference table with the verified tool versions used in this guide — and where to find official documentation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Version Used&lt;/th&gt;
&lt;th&gt;Official Docs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docker Compose&lt;/td&gt;
&lt;td&gt;v2.24+&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;docs.docker.com/compose&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache Airflow&lt;/td&gt;
&lt;td&gt;3.0.4&lt;/td&gt;
&lt;td&gt;&lt;a href="https://airflow.apache.org/docs/" rel="noopener noreferrer"&gt;airflow.apache.org&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.postgresql.org/docs/16/" rel="noopener noreferrer"&gt;postgresql.org/docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;7 (Alpine)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://redis.io/docs/" rel="noopener noreferrer"&gt;redis.io/docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache Spark&lt;/td&gt;
&lt;td&gt;3.5 (Bitnami)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://spark.apache.org/docs/3.5.0/" rel="noopener noreferrer"&gt;spark.apache.org/docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Compose Specification: &lt;a href="https://compose-spec.io" rel="noopener noreferrer"&gt;compose-spec.io&lt;/a&gt; — the authoritative reference for all YAML syntax&lt;/li&gt;
&lt;li&gt;Apache Airflow official Docker Compose setup: &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html" rel="noopener noreferrer"&gt;airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Data Engineering Zoomcamp (DataTalks.Club): practical hands-on course that covers Docker + Airflow + dbt workflows — &lt;a href="https://datatalks.club/courses/data-engineering-zoomcamp.html" rel="noopener noreferrer"&gt;datatalks.club/courses/data-engineering-zoomcamp.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;freeCodeCamp — "How to Use Docker Compose for Production Workloads" (March 2026): covers profiles, watch mode, and GPU support in depth&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Setting up a reliable local data engineering environment used to be a day-long exercise in frustration. With Docker Compose, it's a one-time investment: write the &lt;code&gt;compose.yaml&lt;/code&gt; once, commit it to your repo, and everyone on your team gets the exact same environment with &lt;code&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A few things to remember as you go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drop the &lt;code&gt;version:&lt;/code&gt; field — it's deprecated&lt;/li&gt;
&lt;li&gt;Always use &lt;code&gt;condition: service_healthy&lt;/code&gt; in &lt;code&gt;depends_on&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pin your image versions, not &lt;code&gt;latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Keep credentials in &lt;code&gt;.env&lt;/code&gt;, never in the compose file&lt;/li&gt;
&lt;li&gt;Name your volumes — data you can't recover isn't worth much&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The config in this guide is a starting point. As your stack grows, you'll want to look at override files (&lt;code&gt;compose.override.yaml&lt;/code&gt;) for environment-specific tweaks, and Compose profiles for toggling monitoring tools, debug containers, or test databases without polluting your main setup.&lt;/p&gt;

&lt;p&gt;The whole point is reproducibility. When a teammate clones your repo and runs &lt;code&gt;docker compose up&lt;/code&gt;, they should get exactly what you have. That's not a nice-to-have — it's the baseline for any serious data engineering workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Need a Data Engineer Who Already Knows This Stack?
&lt;/h2&gt;

&lt;p&gt;Honestly, this is where a lot of projects stall. The setup is one thing — building production-grade pipelines on top of it, handling schema drift, optimizing Spark jobs, writing reliable Airflow DAGs that don't silently fail at 2am — that's the real work.&lt;/p&gt;

&lt;p&gt;If your team is scaling and you need someone who has done this before (not just read about it), that's what we do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://lucentinnovation.com" rel="noopener noreferrer"&gt;Lucent Innovation&lt;/a&gt;&lt;/strong&gt; helps product teams and growing companies hire experienced data engineers — people who can own the full stack, from local containerized environments to cloud-scale data infrastructure on AWS, GCP, or Azure.&lt;/p&gt;

&lt;p&gt;Whether you need to &lt;strong&gt;augment your current team&lt;/strong&gt; with a specialist or are looking to &lt;strong&gt;build a data engineering function from scratch&lt;/strong&gt;, we can help you find and place the right person fast — usually within 2–3 weeks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💬 &lt;strong&gt;&lt;a href="https://www.lucentinnovation.com/specialists/hire-data-engineers" rel="noopener noreferrer"&gt;Talk to us about hiring a data engineer →&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
No long intake forms. Just a conversation about what you're building and what you need.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Have questions about this setup or running into issues with a specific service? The comments are open. Also worth checking: the official Apache Airflow Docker Compose documentation gets updated with each release and is often more current than any tutorial you'll find.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>docker</category>
    </item>
    <item>
      <title>Why Your In-House Databricks Team Is Probably Losing You Money</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Wed, 27 May 2026 10:44:20 +0000</pubDate>
      <link>https://dev.to/lucy1/why-your-in-house-databricks-team-is-probably-losing-you-money-35m9</link>
      <guid>https://dev.to/lucy1/why-your-in-house-databricks-team-is-probably-losing-you-money-35m9</guid>
      <description>&lt;p&gt;60% of enterprise AI projects get abandoned because of data readiness and infrastructure issues.&lt;/p&gt;

&lt;p&gt;Not because of bad ideas. Not because of wrong tooling. Because the foundation wasn't built right and by the time anyone noticed, the cost of fixing it was higher than starting over.&lt;/p&gt;

&lt;p&gt;If you're running Databricks in-house, there's a decent chance you're heading toward one of four failure modes. I've seen each of them play out, sometimes in the same org.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The "unicorn engineer" job post
&lt;/h2&gt;

&lt;p&gt;You know the one. It asks for someone who can handle platform architecture, complex ETL pipeline design, MLOps, &lt;em&gt;and&lt;/em&gt; data governance. Maybe Unity Catalog experience preferred. Definitely Spark optimization. Oh, and some Python.&lt;/p&gt;

&lt;p&gt;That person doesn't exist. Or if they do, they're already at a FAANG and not answering your recruiter.&lt;/p&gt;

&lt;p&gt;What actually happens: you hire someone capable, and they spend most of their time on operational noise that manually partitioning tables, babysitting cluster configs, debugging integration issues that have nothing to do with your actual data problems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Databricks has gotten genuinely complex. Delta Lake, Lakeflow Declarative Pipelines, Unity Catalog- these aren't plug-and-play. A generalist data engineer in 2026 is not the same as a Databricks platform specialist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A consulting partner brings people who've already built this across multiple clients. You're not buying hours. You're buying what they learned the hard way somewhere else multi-cloud workspace topology, Liquid Clustering, private endpoint configs without waiting for your team to acquire those scars.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The cloud bill no one is watching
&lt;/h2&gt;

&lt;p&gt;Here's one I've seen kill otherwise solid data platforms quietly.&lt;/p&gt;

&lt;p&gt;In-house team gets the pipelines working. Everyone moves on. Nobody sets up auto-termination. Nobody enforces cluster policies. Clusters run indefinitely. Variable workloads stay on always-on compute when they should be hitting Serverless SQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Traditional In-House Setup] ---&amp;gt; Over-provisioned Clusters ---&amp;gt; High Idle Waste &amp;amp; Skyrocketing Bills
[Consulting-Led Framework] ---&amp;gt; Serverless SQL + Cluster Policies ---&amp;gt; Automated Auto-Termination &amp;amp; Controlled Spend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bill climbs slowly, and then suddenly it's a boardroom conversation.&lt;/p&gt;

&lt;p&gt;A proper FinOps setup isn't exciting work, but it has a direct, measurable line to your cloud costs. Things like mandatory &lt;code&gt;auto_termination_minutes&lt;/code&gt;, enforced instance pool configs, and routing the right workloads away from always-on clusters. This is table stakes, it just often doesn't get done when you're underwater on pipeline work.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Governance that gets bolted on after the fact
&lt;/h2&gt;

&lt;p&gt;The pattern is almost universal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the pipelines&lt;/li&gt;
&lt;li&gt;Ship the dashboards&lt;/li&gt;
&lt;li&gt;Deal with governance "later"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the time "later" arrives, you've got fragmented data silos, ML models stuck in sandbox environments, inconsistent access controls, and no data lineage. Then someone asks about compliance.&lt;/p&gt;

&lt;p&gt;Unity Catalog isn't an afterthought, it's the thing you configure &lt;em&gt;before&lt;/em&gt; the pipelines, not after. Role-based access controls, automated data quality monitoring, end-to-end lineage tracking. If these aren't in the foundation, your downstream reports are unreliable by design.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The uncomfortable truth:&lt;/strong&gt; A lot of teams treat governance like a documentation task. It's not. It's infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. The hiring timeline nobody accounts for
&lt;/h2&gt;

&lt;p&gt;Realistic timeline from job post to a team that's onboarded, trained on Databricks, and actually productive:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6–9 months.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's not pessimism, that's just recruiting + onboarding + platform ramp-up. Most orgs don't factor this in when they're comparing in-house costs against consulting rates.&lt;/p&gt;

&lt;p&gt;A consulting firm gets there faster because they're not starting from scratch. Pre-built IaC templates, established Bronze/Silver/Gold ingestion patterns, CI/CD already wired up. Deployment that takes your internal team six months can happen in weeks.&lt;/p&gt;

&lt;p&gt;That gap matters if your competitors are already running predictive analytics in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what actually works?
&lt;/h2&gt;

&lt;p&gt;It's not a binary choice, and framing it that way is usually how you end up making the wrong call.&lt;/p&gt;

&lt;p&gt;The companies that handle this well use a hybrid model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bring in specialists&lt;/strong&gt; for the hard setup — architecture, Unity Catalog, cluster optimization, MLOps scaffolding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep internal team focused&lt;/strong&gt; on domain knowledge, custom data products, and the business problems that actually need context to solve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your internal engineers understand your data, your customers, and your edge cases. That's valuable and hard to transfer. But asking them to also be platform infrastructure experts is how you end up with both things done poorly.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;In-house default&lt;/th&gt;
&lt;th&gt;What fixes it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Skill gaps&lt;/td&gt;
&lt;td&gt;Overhire, underdeliver&lt;/td&gt;
&lt;td&gt;Consulting for platform-specific work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud costs&lt;/td&gt;
&lt;td&gt;Idle compute, no policies&lt;/td&gt;
&lt;td&gt;FinOps framework from day one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Governance&lt;/td&gt;
&lt;td&gt;Bolted on later&lt;/td&gt;
&lt;td&gt;Unity Catalog before pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;6–9 months to productivity&lt;/td&gt;
&lt;td&gt;Pre-built templates + IaC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The architecture decisions you make in the first few months of a Databricks deployment are surprisingly hard to undo. Getting them right upfront — even with outside help — is almost always cheaper than refactoring a broken foundation at scale.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you gone through a Databricks migration or build-out? Curious what broke first — drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>databricks</category>
      <category>dataengineering</category>
      <category>mlops</category>
      <category>cloudcosts</category>
    </item>
    <item>
      <title>RAG or Fine-Tuning? How We Decide for Our AI Consulting Clients</title>
      <dc:creator>Lucy </dc:creator>
      <pubDate>Thu, 21 May 2026 07:22:26 +0000</pubDate>
      <link>https://dev.to/lucy1/rag-or-fine-tuning-how-we-decide-for-our-ai-consulting-clients-1k27</link>
      <guid>https://dev.to/lucy1/rag-or-fine-tuning-how-we-decide-for-our-ai-consulting-clients-1k27</guid>
      <description>&lt;p&gt;Choosing the right architecture for an artificial intelligence product is one of the most expensive decisions a business can make. When clients come to Lucent Innovation for AI consulting, they often ask the same core question: should we use RAG or fine-tuning? &lt;/p&gt;

&lt;p&gt;Many teams assume they need to train a custom model from scratch to make an AI understand their business. However, making the wrong choice can lead to hundreds of thousands of dollars in wasted cloud computing bills and months of lost development time. &lt;/p&gt;

&lt;p&gt;This guide breaks down the choice in simple, plain English. Whether you are a software engineer building the pipeline or a business leader managing the budget, this framework will help you make the right architectural choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is RAG in AI?
&lt;/h2&gt;

&lt;p&gt;To understand your choices, we must begin with the basics of Retrieval-Augmented Generation. &lt;/p&gt;

&lt;h3&gt;
  
  
  What does RAG stand for in AI?
&lt;/h3&gt;

&lt;p&gt;RAG stands for &lt;strong&gt;Retrieval-Augmented Generation&lt;/strong&gt;. In simple terms, it is an architectural approach that gives a generative AI model an open-book exam. &lt;/p&gt;

&lt;p&gt;Instead of relying solely on what the model learned during its initial training, a RAG AI system looks up real-time information from an external database before it answers a user query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[User Query] ──&amp;gt; [Search External Database] ──&amp;gt; [Retrieve Relevant Text] ──&amp;gt; [Feed into RAG LLM] ──&amp;gt; [Final Accurate Answer]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How does RAG improve the accuracy of generative AI models?
&lt;/h3&gt;

&lt;p&gt;Standard Large Language Models (LLMs) are frozen in time. They only know the data they were trained on. If you ask a standard model about a customer invoice from yesterday, it will either admit it does not know or confidently make up a false answer. This false answer is called a hallucination.&lt;/p&gt;

&lt;p&gt;A RAG LLM setup solves this problem by executing a simple multi-step process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Retrieval Step:&lt;/strong&gt; When a user asks a question, the system searches a private corporate database or vector store for matching documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Augmentation Step:&lt;/strong&gt; The system takes those matching documents and pastes them directly into the hidden prompt background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Generation Step:&lt;/strong&gt; The model reads the question and the pasted documents together, synthesizing a perfectly accurate answer based strictly on the provided facts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By grounding the model in verified data, you eliminate guessing and ensure that the system can access real-time, constantly changing information.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Battle: RAG vs Fine Tuning
&lt;/h2&gt;

&lt;p&gt;While RAG gives the model a library card, LLM fine tuning is completely different. Fine-tuning actually changes the internal brain structure of the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding LLM Fine Tuning
&lt;/h3&gt;

&lt;p&gt;When you fine tune LLM models, you take an existing base model and expose it to a highly specialized dataset for intensive training. This process adjusts the internal weights of the neural network. You are not giving the model an open-book exam: you are sending it back to school to learn a specific style, dialect, or structural format.&lt;/p&gt;

&lt;p&gt;Here is an engineering visual to help conceptualize the foundational pathways:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxcry94wrqvyv804dhko1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxcry94wrqvyv804dhko1.png" alt="Understanding LLM Fine Tuning"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  RAG vs LLM: The Core Differences
&lt;/h3&gt;

&lt;p&gt;To see why this matters for your engineering budget, consider this comparison table of operational trade-offs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evaluation Feature&lt;/th&gt;
&lt;th&gt;RAG AI Systems&lt;/th&gt;
&lt;th&gt;LLM Fine Tuning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Knowledge Base Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dynamic and real-time external data&lt;/td&gt;
&lt;td&gt;Static snapshot baked into the model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Finding specific facts and text chunks&lt;/td&gt;
&lt;td&gt;Learning a specific style, tone, or format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hallucination Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very high: sources can be cited directly&lt;/td&gt;
&lt;td&gt;Low: can still invent facts if prompt is weak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Upfront Setup Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low to moderate developer hours&lt;/td&gt;
&lt;td&gt;High compute costs and specialized data engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Privacy Boundaries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Easy to restrict data via database permissions&lt;/td&gt;
&lt;td&gt;Difficult to restrict access once data is baked in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  When to Use Fine Tuning vs RAG?
&lt;/h2&gt;

&lt;p&gt;The choice between fine tuning vs RAG comes down to a simple engineering rule: Use RAG for knowledge, and use fine-tuning for behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Unique Lucent Innovation Point of View: The Data Lifecycle Reality
&lt;/h3&gt;

&lt;p&gt;Most online guides tell you to evaluate your choice based purely on accuracy. At Lucent Innovation, we tell our enterprise clients to look at something completely different: look at &lt;strong&gt;who owns the data&lt;/strong&gt; and &lt;strong&gt;how fast it changes&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;If your data changes every hour, every day, or every week, fine tuning LLMs is a terrible operational trap. The moment your business updates a pricing sheet or changes a product feature, your fine-tuned model becomes obsolete. You would have to spend thousands of dollars to retrain it again. &lt;/p&gt;

&lt;p&gt;RAG fine tuning decisions should follow these strict operational guidelines:&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose RAG when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need to connect your AI to live business documents, customer support wikis, or internal Slack logs.&lt;/li&gt;
&lt;li&gt;You must show users exactly where the information came from by providing source citations and links.&lt;/li&gt;
&lt;li&gt;You need to build your product quickly without renting expensive GPU clusters for training cycles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose Fine-Tuning when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need the model to output perfect, strict JSON code structures every single time without fail.&lt;/li&gt;
&lt;li&gt;You want the AI to perfectly mimic a specific person's copywriting style, voice, or industry jargon.&lt;/li&gt;
&lt;li&gt;You are working with an ultra-niche domain (like advanced medical pathology reports or ancient legal statutes) that the base model cannot comprehend.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  RAG vs Fine Tuning vs Prompt Engineering?
&lt;/h2&gt;

&lt;p&gt;Before jumping into a complex software architecture, engineers should always evaluate the entire spectrum of optimization. This brings us to a three-way comparison: RAG vs fine tuning vs prompt engineering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Prompt Engineering] ──&amp;gt; Simple instructions in the text box (Minutes to set up)
[RAG Architecture]   ──&amp;gt; Hooking up a search engine to the text box (Days to set up)
[Fine-Tuning]        ──&amp;gt; Re-wiring the underlying engine itself (Weeks to set up)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prompt engineering is the foundation. It involves writing clever, descriptive instructions directly inside your system prompt. For instance, telling a model to "act like a professional accountant" is prompt engineering. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Decision Spectrum
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt Engineering:&lt;/strong&gt; Best for fast prototyping, basic text transformations, and setting up initial rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG vs Prompt Engineering:&lt;/strong&gt; When your system prompt gets too full of information, it hits a wall. Standard context windows can become slow and expensive. That is when you step up to RAG, which selectively feeds only the relevant data chunks into the prompt instead of dumping the entire database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-Tuning:&lt;/strong&gt; The final step. Once your RAG system knows &lt;em&gt;what&lt;/em&gt; to say, you can use fine-tuning to perfect &lt;em&gt;how&lt;/em&gt; it says it, shrinking your prompt sizes and reducing latency.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Real World Client Scenario: How We Consult
&lt;/h2&gt;

&lt;p&gt;To make this practical, let us look at a real architecture challenge we solved for one of our enterprise consulting clients.&lt;/p&gt;

&lt;p&gt;The client wanted an AI assistant to help their customer success team look up technical product specifications and write email responses in the company's precise tone of voice.&lt;/p&gt;

&lt;p&gt;Instead of picking just one path, we deployed a hybrid strategy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The RAG Layer:&lt;/strong&gt; We hooked up their product documentation manuals to a vector database pipeline. This ensured that the AI always retrieved 100 percent accurate product specifications, eliminating hallucinations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Fine-Tuning Layer:&lt;/strong&gt; We took the base open-source model and fine-tuned it on 5,000 historical customer service emails that were manually approved by their marketing team. This taught the model's brain to always write responses with a helpful, warm, and structured corporate tone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By combining the open-book access of RAG with the behavioral habits of fine-tuning, the client achieved a 40 percent reduction in average ticket handling time while keeping errors at absolute zero.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Designing Your AI Roadmap
&lt;/h2&gt;

&lt;p&gt;There is no single winner in the battle of RAG vs fine tuning. They are complementary tools designed for completely different software problems.&lt;/p&gt;

&lt;p&gt;If your product goals require access to fresh facts, internal knowledge bases, and clear data source tracking, building a RAG framework is your optimal choice. If your product demands strict adherence to complex code layouts or deep alignment with a specific brand persona, investing in custom weights is the right path forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get Expert Engineering Guidance
&lt;/h3&gt;

&lt;p&gt;Navigating these architectural decisions requires deep hands-on experience. Making a mistake early in your development cycle can result in severe technical debt and bloated maintenance costs.&lt;/p&gt;

&lt;p&gt;At Lucent Innovation, we specialize in helping businesses design, build, and optimize high-performance AI systems that drive real business outcomes. We analyze your data dynamics, security requirements, and budget constraints to engineer the perfect pipeline for your platform.&lt;/p&gt;

&lt;p&gt;Are you unsure which approach fits your upcoming product? This is exactly what our engineering team helps clients figure out every day. Let us protect your runway and accelerate your deployment timeline. &lt;a href="https://www.lucentinnovation.com/services/ai-consulting" rel="noopener noreferrer"&gt;Book a free discovery call with the Lucent Innovation AI consulting team today&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Foundational Sources &amp;amp; Technical Reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Learn more about the mechanics of &lt;a href="https://www.databricks.com" rel="noopener noreferrer"&gt;Retrieval-Augmented Generation on the Databricks Lakehouse Platform Architecture&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Review foundational research and code guidelines on &lt;a href="https://platform.openai.com" rel="noopener noreferrer"&gt;Large Language Model Fine-Tuning via OpenAI Developer Documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Explore semantic indexing protocols via the &lt;a href="https://www.pinecone.io" rel="noopener noreferrer"&gt;Pinecone Vector Database Engineering Blog&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
