<?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: Liam Martin</title>
    <description>The latest articles on DEV Community by Liam Martin (@liammartin).</description>
    <link>https://dev.to/liammartin</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%2F4086802%2F698a0189-b3f0-4b93-9c9e-a7e2fa891585.png</url>
      <title>DEV Community: Liam Martin</title>
      <link>https://dev.to/liammartin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/liammartin"/>
    <language>en</language>
    <item>
      <title>I migrated a production app to Next.js App Router. Here is what the docs don't tell you.</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Wed, 09 Sep 2026 21:29:56 +0000</pubDate>
      <link>https://dev.to/liammartin/i-migrated-a-production-app-to-nextjs-app-router-here-is-what-the-docs-dont-tell-you-17dn</link>
      <guid>https://dev.to/liammartin/i-migrated-a-production-app-to-nextjs-app-router-here-is-what-the-docs-dont-tell-you-17dn</guid>
      <description>&lt;p&gt;If you search for "Next.js App Router migration," you will find dozens of perfectly polished tutorials. They show you how to move a simple blog from the old &lt;code&gt;pages&lt;/code&gt; directory to the new &lt;code&gt;app&lt;/code&gt; directory in 15 minutes. &lt;/p&gt;

&lt;p&gt;I am here to talk about what happens when you try to migrate a messy, real-world, highly interactive SaaS application with thousands of active users. &lt;/p&gt;

&lt;p&gt;Spoiler: It took us three weeks, broke our authentication flow twice, and completely shattered how I thought about web development. But we made it out the other side, and the performance gains are actually real. &lt;/p&gt;

&lt;p&gt;If you are planning this migration, here are the hidden traps you need to watch out for.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;"use client"&lt;/code&gt; Contagion
&lt;/h3&gt;

&lt;p&gt;When you first read about React Server Components (RSC), the pitch sounds amazing. Send zero JavaScript to the client! Fetch data right on the server! &lt;/p&gt;

&lt;p&gt;So, I started moving our dashboard over. I created a server component, fetched the user data, and passed it down to a child component. But wait, that child component has a dropdown menu that needs &lt;code&gt;useState&lt;/code&gt;. So, I added &lt;code&gt;"use client"&lt;/code&gt; to the top of that file. &lt;/p&gt;

&lt;p&gt;Suddenly, I realized that the dropdown imported a context provider, which imported our theme configuration, which imported half of our component library. Before I knew it, a single &lt;code&gt;"use client"&lt;/code&gt; directive at the top of a file had cascaded down, turning 80% of my fancy new Server Components back into Client Components. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; You have to completely rewire your brain to push interactivity to the absolute edges of your component tree. We spent days refactoring our UI just to isolate the stateful parts into tiny, wrapping components so the parent layouts could stay on the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Caching Will Drive You Crazy
&lt;/h3&gt;

&lt;p&gt;Next.js App Router caches everything by default. I mean &lt;em&gt;everything&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;I deployed our staging environment and noticed that when a user updated their profile picture, the old picture kept showing up. I refreshed the page. Old picture. I hard-refreshed. Old picture. &lt;/p&gt;

&lt;p&gt;I went down a massive rabbit hole trying to understand the difference between the Data Cache, the Full Route Cache, the Router Cache, and the unholy mess of &lt;code&gt;revalidateTag&lt;/code&gt; vs &lt;code&gt;revalidatePath&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The documentation explains how these work in isolation, but debugging them in a live environment is brutal. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Start dumb. We ended up explicitly opting out of caching on almost all of our authenticated routes by using &lt;code&gt;export const dynamic = 'force-dynamic'&lt;/code&gt; at the top of our page files. Once the app was stable and working predictably, we slowly started layering the cache back in where it actually mattered (like public-facing marketing pages).&lt;/p&gt;

&lt;h3&gt;
  
  
  Third-Party Libraries Aren't Ready
&lt;/h3&gt;

&lt;p&gt;This was the biggest roadblock. We rely heavily on a popular drag-and-drop library and an analytics wrapper. Both of them immediately threw errors about &lt;code&gt;window&lt;/code&gt; being undefined the second they touched a Server Component. &lt;/p&gt;

&lt;p&gt;You will spend a significant amount of your migration writing wrapper components just to make old libraries play nice with the new paradigm. &lt;/p&gt;

&lt;h3&gt;
  
  
  Was it worth it?
&lt;/h3&gt;

&lt;p&gt;Honestly, week two of the migration was miserable. I was ready to roll back to the &lt;code&gt;pages&lt;/code&gt; directory and never look at Server Components again.&lt;/p&gt;

&lt;p&gt;But now that we are on the other side? Our initial page load times dropped by 40%. Our bundle size is a fraction of what it used to be. Writing async server components that just hit the database directly without needing to set up a separate API route feels like a superpower.&lt;/p&gt;

&lt;p&gt;If you are about to start this migration: don't do it in one giant pull request. Move your static pages first, wrap your providers carefully, and accept that you are going to spend a lot of time fighting the cache.&lt;/p&gt;

&lt;p&gt;Has anyone else survived this migration yet, or are you all staying on the &lt;code&gt;pages&lt;/code&gt; router until the end of time?&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>My DEV.to posts are flagged with noindex nofollow tags</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Wed, 09 Sep 2026 00:19:41 +0000</pubDate>
      <link>https://dev.to/liammartin/my-devto-posts-are-flagged-with-noindex-nofollow-tags-14mb</link>
      <guid>https://dev.to/liammartin/my-devto-posts-are-flagged-with-noindex-nofollow-tags-14mb</guid>
      <description>&lt;p&gt;Hi, I've been posting for a while and I've noticed some kind of shadowban on my posts, since they don't appear in search results (neither from dev.to nor google). I think there's also a problem with the 'writing posts' badges.&lt;/p&gt;

&lt;p&gt;I've contacted support several times but it doesn't seem to help. Anyone any idea what can I do? I'd like to keep contributing to this awesome community and I don't want to get discouraged by this issue. If someone could help me I would appreciate :)&lt;/p&gt;

</description>
      <category>devto</category>
    </item>
    <item>
      <title>Deploying GPT-6 Astra: Surviving the Next Gen of LLMs in Production</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Tue, 08 Sep 2026 15:17:33 +0000</pubDate>
      <link>https://dev.to/liammartin/deploying-gpt-6-astra-surviving-the-next-gen-of-llms-in-production-591f</link>
      <guid>https://dev.to/liammartin/deploying-gpt-6-astra-surviving-the-next-gen-of-llms-in-production-591f</guid>
      <description>&lt;p&gt;The release of GPT-6 Astra has officially shifted the goalposts. We are no longer just talking about massive parameter counts; we are dealing with multi-modal real-time reasoning, dynamic context windows, and an architecture that demands absolute respect from your infrastructure. &lt;/p&gt;

&lt;p&gt;If you are a DevOps engineer or a backend developer tasked with putting Astra into production, you already know that a simple API wrapper won't cut it. Serving this beast requires a fundamental rethink of how we handle inference, memory scaling, and latency.&lt;/p&gt;

&lt;p&gt;Here is a pragmatic guide to surviving a GPT-6 Astra deployment without burning your entire cloud budget in a weekend.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Hardware Reality Check: Distributed Inference
&lt;/h2&gt;

&lt;p&gt;Astra is too large to fit on a single standard GPU, even if you are rocking the latest H200s or B200s. You need a multi-node cluster, and that means &lt;strong&gt;Tensor Parallelism (TP)&lt;/strong&gt; and &lt;strong&gt;Pipeline Parallelism (PP)&lt;/strong&gt; are no longer optional—they are mandatory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Tensor Parallelism:&lt;/strong&gt; Splits individual matrix operations across multiple GPUs within the same node. This requires high-bandwidth interconnects like NVLink.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Pipeline Parallelism:&lt;/strong&gt; Slices the model's layers across different nodes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Use frameworks like Ray Serve or vLLM heavily tuned for distributed setups. If you aren't partitioning your model correctly, your GPUs will spend more time waiting for data over the network than actually computing tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Taming the KV Cache
&lt;/h2&gt;

&lt;p&gt;Astra’s dynamic context window is incredible for users but a nightmare for VRAM. The Key-Value (KV) cache stores the attention tensors for previous tokens. With Astra's extended context, this cache can quickly grow larger than the model weights themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter PagedAttention
&lt;/h3&gt;

&lt;p&gt;If you are deploying Astra, you absolutely must use an inference engine that supports &lt;strong&gt;PagedAttention&lt;/strong&gt;. By managing the KV cache memory in non-contiguous blocks (much like an OS handles virtual memory), you can reduce memory fragmentation to near zero and increase your batch sizes by up to 5x. &lt;/p&gt;

&lt;h2&gt;
  
  
  3. Optimization: Quantization and Speculative Decoding
&lt;/h2&gt;

&lt;p&gt;Running Astra at FP16 (16-bit floating point) in production is financial suicide for most startups. You need to quantize without losing that sweet Astra reasoning capability.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;FP8 / INT4 Quantization:&lt;/strong&gt; Techniques like AWQ (Activation-aware Weight Quantization) or SmoothQuant are your best friends here. They compress the model footprint drastically while maintaining near-baseline accuracy.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Speculative Decoding:&lt;/strong&gt; This is the secret sauce for low-latency Astra deployments. Use a smaller, faster "draft" model to predict the next few tokens, and use Astra solely to verify them in a single forward pass. If the draft model is accurate, you effectively generate multiple tokens in the time it takes Astra to generate one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Production Architecture: The API Gateway
&lt;/h2&gt;

&lt;p&gt;You cannot expose Astra directly to your frontend. It needs a robust shield. Your API Gateway must handle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Semantic Caching:&lt;/strong&gt; Don't compute the same query twice. Implement a semantic cache (like Redis + vector embeddings) to serve identical or highly similar queries instantly.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dynamic Rate Limiting:&lt;/strong&gt; Token generation speed fluctuates. Rate limit by &lt;em&gt;compute tokens&lt;/em&gt;, not just by requests.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Circuit Breakers:&lt;/strong&gt; If the cluster gets overwhelmed, fail fast and degrade gracefully rather than hanging user requests for 30 seconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Deploying GPT-6 Astra isn't just an AI problem; it's a hardcore distributed systems problem. The developers who win in this era won't necessarily be the ones with the best prompts, but the ones who can squeeze every last drop of efficiency out of their inference pipelines.&lt;/p&gt;

&lt;p&gt;Have you started messing around with Astra's deployment yet? What's your current infrastructure stack looking like? Drop your setups in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Rise of Local AI: Why You Should Run LLMs on Your Laptop</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Mon, 07 Sep 2026 17:31:51 +0000</pubDate>
      <link>https://dev.to/liammartin/the-rise-of-local-ai-why-you-should-run-llms-on-your-laptop-1i1k</link>
      <guid>https://dev.to/liammartin/the-rise-of-local-ai-why-you-should-run-llms-on-your-laptop-1i1k</guid>
      <description>&lt;p&gt;If you are a developer in 2026, you have probably integrated an AI API into at least one of your projects. The cloud-based giants are incredibly powerful, but relying entirely on external APIs comes with a catch: latency, subscription costs, and strict data privacy concerns.&lt;/p&gt;

&lt;p&gt;That is exactly why the developer community is experiencing a massive shift toward &lt;strong&gt;Local AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to highly optimized open-weights models and incredible tooling, running a Large Language Model (LLM) on your own hardware is no longer just a weekend science experiment—it is a viable architecture choice. Let's talk about why you should consider moving your AI workloads to your local machine and how you can get started in under five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Go Local?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Absolute Data Privacy
&lt;/h3&gt;

&lt;p&gt;When you send user data or proprietary code to a cloud API, it leaves your ecosystem. For healthcare, finance, or enterprise applications, this is often a dealbreaker. Local LLMs process everything on-device. If you cut your Wi-Fi, the model still works perfectly, guaranteeing that your data never leaves your machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Zero Recurring Costs
&lt;/h3&gt;

&lt;p&gt;API costs scale with your user base. While cents per thousand tokens sounds cheap at first, it adds up quickly in high-traffic applications, especially when building Agentic workflows that loop multiple times. Local AI turns an ongoing operational expense (OpEx) into a one-time hardware investment.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Latency and Offline Development
&lt;/h3&gt;

&lt;p&gt;There is nothing worse than being blocked on a flight or in a coffee shop with spotty Wi-Fi because your dev environment relies on cloud endpoints. Local models give you instant responses with zero network latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic of Ollama
&lt;/h2&gt;

&lt;p&gt;A year ago, setting up a local model meant wrestling with Python dependencies, CUDA drivers, and hardware configurations. Today, tools like &lt;strong&gt;Ollama&lt;/strong&gt; have made running an LLM as easy as running a Docker container.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's Build: Your First Local AI Script
&lt;/h3&gt;

&lt;p&gt;Here is how easily you can get a local model running and talk to it via Python.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Install Ollama
&lt;/h4&gt;

&lt;p&gt;Head over to the Ollama website and download the installer for your OS.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Pull a Model
&lt;/h4&gt;

&lt;p&gt;Open your terminal and pull a fast, efficient model. We will use Meta's Llama 3 (the 8B parameter version is perfect for most laptops):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run llama3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Connect with Python
&lt;/h4&gt;

&lt;p&gt;You don't need complex frameworks to get started. Just install the official Python library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, write a quick script to generate a response:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_local_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Thinking...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;llama3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&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;role&lt;/span&gt;&lt;span class="sh"&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;user&lt;/span&gt;&lt;span class="sh"&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;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&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="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&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;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Test it out
&lt;/span&gt;&lt;span class="n"&gt;user_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain why caching is important in web development in two sentences.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generate_local_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that script, and you will see a response generated entirely by your CPU/GPU, with no internet connection required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;Cloud models are not going anywhere—they will always win on raw parameter count and reasoning capabilities for the most complex tasks. But for 80% of daily developer tasks (summarization, local code assistance, RAG pipelines over personal documents, and basic chat interfaces), local models are more than capable.&lt;/p&gt;

&lt;p&gt;Are you running any models locally yet? If so, what is your go-to model and toolstack right now? Let's chat in the comments!&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stop Fetching Data in useEffect: Embrace Modern React Patterns</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Sat, 05 Sep 2026 16:45:11 +0000</pubDate>
      <link>https://dev.to/liammartin/stop-fetching-data-in-useeffect-embrace-modern-react-patterns-2do6</link>
      <guid>https://dev.to/liammartin/stop-fetching-data-in-useeffect-embrace-modern-react-patterns-2do6</guid>
      <description>&lt;p&gt;If you've been working with React for a while, you probably have a muscle memory for fetching data inside a &lt;code&gt;useEffect&lt;/code&gt; hook. For years, it was the standard way to grab data on component mount. But as the React ecosystem matures, relying on &lt;code&gt;useEffect&lt;/code&gt; for data fetching has become a widely recognized anti-pattern.&lt;/p&gt;

&lt;p&gt;Here is why you should upgrade your approach and what you should be using instead to build faster, more reliable applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with &lt;code&gt;useEffect&lt;/code&gt; Fetching
&lt;/h2&gt;

&lt;p&gt;Fetching data directly inside &lt;code&gt;useEffect&lt;/code&gt; introduces several silent issues into your application that can harm user experience and create maintenance headaches.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Race Conditions:&lt;/strong&gt; If a component re-renders quickly, multiple network requests can fire and resolve out of order, leading to unpredictable UI states.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Built-in Caching:&lt;/strong&gt; Every time the component mounts, it fetches the data again from scratch, wasting bandwidth and slowing down the experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate Overload:&lt;/strong&gt; You must manually manage &lt;code&gt;loading&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, and &lt;code&gt;data&lt;/code&gt; states for every single request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Waterfall Requests:&lt;/strong&gt; Child components cannot start fetching their own data until the parent component finishes loading and renders them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Modern Alternatives: Server Components and Libraries
&lt;/h2&gt;

&lt;p&gt;Instead of reinventing the wheel with complex state management, the React community has shifted towards purpose-built libraries and new architectural paradigms.&lt;/p&gt;

&lt;p&gt;If you are using a modern framework like Next.js, &lt;strong&gt;React Server Components (RSC)&lt;/strong&gt; allow you to fetch data securely on the server without shipping unnecessary JavaScript to the client. You can fetch data directly in your component using standard &lt;code&gt;async/await&lt;/code&gt; syntax.&lt;/p&gt;

&lt;p&gt;For client-side fetching, libraries like &lt;strong&gt;TanStack Query (React Query)&lt;/strong&gt; or &lt;strong&gt;SWR&lt;/strong&gt; are the gold standard. They handle the heavy lifting out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Caching:&lt;/strong&gt; Data is cached locally, making subsequent page loads feel instant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale-While-Revalidate:&lt;/strong&gt; The UI displays cached data immediately while silently pulling fresh data in the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified State:&lt;/strong&gt; You get &lt;code&gt;isLoading&lt;/code&gt; and &lt;code&gt;isError&lt;/code&gt; flags automatically without needing extra &lt;code&gt;useState&lt;/code&gt; hooks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Moving away from &lt;code&gt;useEffect&lt;/code&gt; for data fetching will make your codebase cleaner, faster, and much more resilient to network inconsistencies. If you haven't explored Server Components or dedicated fetching libraries yet, your next side project is the perfect place to start.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Build a Simple E-Commerce App: The Ultimate PHP Shopping Cart Tutorial</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Thu, 03 Sep 2026 07:53:49 +0000</pubDate>
      <link>https://dev.to/liammartin/build-a-simple-e-commerce-app-the-ultimate-php-shopping-cart-tutorial-26hh</link>
      <guid>https://dev.to/liammartin/build-a-simple-e-commerce-app-the-ultimate-php-shopping-cart-tutorial-26hh</guid>
      <description>&lt;p&gt;If you are diving into the world of web development, building an e-commerce feature is a rite of passage. It teaches you about state management, handling user input, and processing data. Today, we are going to walk through a complete, beginner-friendly &lt;strong&gt;PHP shopping cart tutorial&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By the end of this guide, you will have a functional, session-based shopping cart where users can add products, view their cart, and clear it. No complex frameworks—just pure, vanilla PHP.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We will focus purely on the core logic using PHP sessions, setting the perfect foundation before you connect it to a MySQL database later.&lt;/em&gt;&lt;/p&gt;

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

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

&lt;p&gt;Before we start coding, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A local development environment installed (like XAMPP, MAMP, or Laravel Valet).&lt;/li&gt;
&lt;li&gt;Basic knowledge of PHP and HTML.&lt;/li&gt;
&lt;li&gt;A code editor (VS Code is always a solid choice).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up the Products and Session
&lt;/h2&gt;

&lt;p&gt;Because PHP is stateless, it forgets everything about a user as soon as the page loads. To build a shopping cart, we need a way to remember what the user clicked on. Enter &lt;strong&gt;PHP Sessions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Create a file named &lt;code&gt;index.php&lt;/code&gt;. At the very top of this file, we will start our session and define a mock array of products. In a real-world application, you would fetch these from a MySQL database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;session_start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize the cart if it doesn't exist&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cart'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cart'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;// Mock database of products&lt;/span&gt;
&lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Mechanical Keyboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;120.00&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Wireless Mouse'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;50.00&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Monitor Stand'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;35.00&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Displaying the Storefront
&lt;/h2&gt;

&lt;p&gt;Now that we have our products, let's display them so users can actually click "Add to Cart". Add this HTML below your PHP logic in &lt;code&gt;index.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;PHP Shopping Cart Tutorial&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3b49df&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;}&lt;/span&gt;
        &lt;span class="nc"&gt;.btn&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2f3ab2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Awesome Dev Store&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"products-grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;?php foreach ($products as $id =&amp;gt; $product): ?&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php echo $product['name']; ?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;$&lt;span class="cp"&gt;&amp;lt;?php echo number_format($product['price'], 2); ?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- We pass the product ID via the URL --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"cart.php?action=add&amp;amp;id=&amp;lt;?php echo $id; ?&amp;gt;"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add to Cart&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;?php endforeach; ?&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"cart.php"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;View Cart (&lt;span class="cp"&gt;&amp;lt;?php echo count($_SESSION['cart']); ?&amp;gt;&lt;/span&gt; items)&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Handling Cart Logic
&lt;/h2&gt;

&lt;p&gt;Now comes the core of our &lt;strong&gt;PHP shopping cart tutorial&lt;/strong&gt;. We need to create a &lt;code&gt;cart.php&lt;/code&gt; file that handles three main actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adding an item.&lt;/li&gt;
&lt;li&gt;Clearing the cart.&lt;/li&gt;
&lt;li&gt;Displaying the cart contents.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create &lt;code&gt;cart.php&lt;/code&gt; and paste the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;session_start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Redirect back to index if cart doesn't exist&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cart'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cart'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;// Handle Add to Cart action&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'action'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'action'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'add'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;intval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// If item is already in cart, increase quantity&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cart'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cart'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'quantity'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Add new item to cart&lt;/span&gt;
        &lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cart'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'quantity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Redirect to avoid form resubmission on refresh&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Location: cart.php'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Handle Clear Cart action&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'action'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'action'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'clear'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cart'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Location: cart.php'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Same mock database for pricing reference&lt;/span&gt;
&lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Mechanical Keyboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;120.00&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Wireless Mouse'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;50.00&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Monitor Stand'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;35.00&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Displaying the Cart
&lt;/h2&gt;

&lt;p&gt;Finally, let's render the cart in the same &lt;code&gt;cart.php&lt;/code&gt; file so the user can see what they are about to buy. Append this HTML to &lt;code&gt;cart.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Your Cart&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-collapse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;collapse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;}&lt;/span&gt;
        &lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3b49df&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;}&lt;/span&gt;
        &lt;span class="nc"&gt;.btn-danger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#df3b3b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Your Shopping Cart&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

    &lt;span class="cp"&gt;&amp;lt;?php if (empty($_SESSION['cart'])): ?&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Your cart is currently empty.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;?php else: ?&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Product&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Price&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Quantity&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Subtotal&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
                &lt;span class="cp"&gt;&amp;lt;?php 
                $total = 0;
                foreach ($_SESSION['cart'] as $id =&amp;gt; $item): 
                    $product = $products[$id];
                    $subtotal = $product['price'] * $item['quantity'];
                    $total += $subtotal;
                ?&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php echo $product['name']; ?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;$&lt;span class="cp"&gt;&amp;lt;?php echo number_format($product['price'], 2); ?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php echo $item['quantity']; ?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;$&lt;span class="cp"&gt;&amp;lt;?php echo number_format($subtotal, 2); ?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
                &lt;span class="cp"&gt;&amp;lt;?php endforeach; ?&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;tfoot&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"text-align: right;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Total:&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;$&lt;span class="cp"&gt;&amp;lt;?php echo number_format($total, 2); ?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/tfoot&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"cart.php?action=clear"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-danger"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Empty Cart&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;?php endif; ?&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"index.php"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;← Continue Shopping&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;And there you have it! You've just completed a working prototype from this PHP shopping cart tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps for Production:
&lt;/h3&gt;

&lt;p&gt;While this is a great learning exercise, you will want to upgrade a few things before putting this on a live server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Database Integration:&lt;/strong&gt; Swap the &lt;code&gt;$products&lt;/code&gt; array for PDO or MySQLi queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Sanitize your URL inputs to prevent XSS and SQL injection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checkout Gateways:&lt;/strong&gt; Integrate a service like Stripe or PayPal to handle the actual money.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you found this tutorial helpful, drop a like or let me know in the comments how you plan to implement this in your next project. Happy coding! 👨‍💻👩‍💻&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>php</category>
    </item>
    <item>
      <title>Step-by-Step Web Scraper Tutorial: Python Data Extraction for Beginners</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Tue, 01 Sep 2026 12:09:27 +0000</pubDate>
      <link>https://dev.to/liammartin/step-by-step-web-scraper-tutorial-python-data-extraction-for-beginners-2j0m</link>
      <guid>https://dev.to/liammartin/step-by-step-web-scraper-tutorial-python-data-extraction-for-beginners-2j0m</guid>
      <description>&lt;p&gt;If you have ever needed to gather data from a website but dreaded the thought of copying and pasting for hours, you are in the right place. In this &lt;strong&gt;web scraper tutorial, Python&lt;/strong&gt; will be our weapon of choice to automate the heavy lifting. &lt;/p&gt;

&lt;p&gt;Python is widely considered the best language for web scraping due to its simple syntax and incredibly powerful community libraries. By the end of this guide, you will know exactly how to fetch a webpage, parse its HTML, extract the exact data you want, and save it to a CSV file.&lt;/p&gt;

&lt;p&gt;Let's dive in and build your first scraper.&lt;/p&gt;

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

&lt;p&gt;Before we start writing code, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Python 3&lt;/strong&gt; installed on your machine.&lt;/li&gt;
&lt;li&gt;  A basic understanding of HTML (tags, classes, and IDs).&lt;/li&gt;
&lt;li&gt;  A code editor like VS Code or PyCharm.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Install the Required Libraries
&lt;/h2&gt;

&lt;p&gt;We will use two essential Python packages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Requests:&lt;/strong&gt; To send HTTP requests and download the HTML of the webpage.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;BeautifulSoup 4:&lt;/strong&gt; To parse the HTML and easily navigate the data tree.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open your terminal or command prompt and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests beautifulsoup4 pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Note: We are also installing &lt;code&gt;pandas&lt;/code&gt; to easily export our scraped data later).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Fetch the Target Webpage
&lt;/h2&gt;

&lt;p&gt;For this tutorial, we will scrape &lt;a href="http://quotes.toscrape.com/" rel="noopener noreferrer"&gt;Quotes to Scrape&lt;/a&gt;, a sandbox website specifically designed for developers to practice web scraping safely.&lt;/p&gt;

&lt;p&gt;Create a new file named &lt;code&gt;scraper.py&lt;/code&gt; and add the following code:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Define the target URL
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[http://quotes.toscrape.com/](http://quotes.toscrape.com/)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Send an HTTP GET request
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Check if the request was successful
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Successfully fetched the webpage!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&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;Failed to retrieve data. Status code: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this script. If you see "Successfully fetched the webpage!", you are ready for the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Parse the HTML with BeautifulSoup
&lt;/h2&gt;

&lt;p&gt;Now that we have the raw HTML, we need to make it readable and searchable. That is exactly what BeautifulSoup does.&lt;/p&gt;

&lt;p&gt;Update your &lt;code&gt;scraper.py&lt;/code&gt; file:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[http://quotes.toscrape.com/](http://quotes.toscrape.com/)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Parse the HTML content
&lt;/span&gt;&lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Print the title of the page to verify it worked
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Extract the Data You Need
&lt;/h2&gt;

&lt;p&gt;If you inspect the target website using your browser's Developer Tools (Right-click -&amp;gt; Inspect), you will notice that every quote is wrapped inside a &lt;code&gt;&amp;lt;div class="quote"&amp;gt;&lt;/code&gt;. Inside that container, the text has a class of text, and the author has a class of author.&lt;/p&gt;

&lt;p&gt;Let's write a loop to find all these quotes and extract the text and author name.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[http://quotes.toscrape.com/](http://quotes.toscrape.com/)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Find all div elements with the class 'quote'
&lt;/span&gt;&lt;span class="n"&gt;quotes_elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quote&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;scraped_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="c1"&gt;# Loop through the elements and extract text and author
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;quotes_elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;small&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;author&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

    &lt;span class="c1"&gt;# Store in a dictionary
&lt;/span&gt;    &lt;span class="n"&gt;scraped_data&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;Quote&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Author&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nf"&gt;print&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;author&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; said: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&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;h2&gt;
  
  
  Step 5: Save the Data to a CSV
&lt;/h2&gt;

&lt;p&gt;Printing data to the console is great for testing, but in real-world scenarios, you want to save it. We will use &lt;code&gt;pandas&lt;/code&gt; to quickly convert our list of dictionaries into a clean CSV file.&lt;/p&gt;

&lt;p&gt;Here is the final, complete code for your scraper:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&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;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[http://quotes.toscrape.com/](http://quotes.toscrape.com/)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="nf"&gt;print&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;Scraping data from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to fetch the page.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;quotes_elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quote&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;scraped_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;quotes_elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;span&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;small&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;author&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

        &lt;span class="n"&gt;scraped_data&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;Quote&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Author&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert to a DataFrame and save to CSV
&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;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scraped_data&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="nf"&gt;to_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;quotes.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;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data successfully saved to quotes.csv!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3 Golden Rules of Web Scraping
&lt;/h2&gt;

&lt;p&gt;Before you take your new skills out into the wild, keep these best practices in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the &lt;code&gt;robots.txt&lt;/code&gt;:&lt;/strong&gt; Always check &lt;code&gt;website.com/robots.txt&lt;/code&gt; to see what pages the site owners allow or disallow scrapers to visit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limit Your Requests:&lt;/strong&gt; Do not hammer a server with hundreds of requests a second. Use &lt;code&gt;time.sleep(2)&lt;/code&gt; to pause between requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't Scrape Private Data:&lt;/strong&gt; Only scrape publicly available data that doesn't require bypassing authentications unethically.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Congratulations! You have successfully completed this web scraper tutorial. Python makes it incredibly intuitive to automate data collection, and this simple foundation can be scaled up to scrape multiple pages, handle login forms, or interact with APIs.&lt;/p&gt;

&lt;p&gt;If you found this guide helpful, drop a like or let me know in the comments what website you plan on scraping first! Happy coding!&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AI is Writing Our Code, But Who is Going to Maintain It?</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Mon, 31 Aug 2026 20:40:23 +0000</pubDate>
      <link>https://dev.to/liammartin/ai-is-writing-our-code-but-who-is-going-to-maintain-it-42h2</link>
      <guid>https://dev.to/liammartin/ai-is-writing-our-code-but-who-is-going-to-maintain-it-42h2</guid>
      <description>&lt;p&gt;Let’s be honest: we all use AI now. Whether it’s Copilot auto-completing our loops, Cursor writing our boilerplate, or Claude generating entire React components, the era of writing every single line of code by hand is officially over. &lt;/p&gt;

&lt;p&gt;And on the surface, it looks like a productivity miracle. PRs are merged faster, features are shipped in days instead of weeks, and the initial velocity of projects has gone through the roof.&lt;/p&gt;

&lt;p&gt;But as someone who spends most of their day reviewing code and maintaining production systems, I’m seeing a massive, terrifying storm gathering on the horizon. &lt;/p&gt;

&lt;p&gt;We are generating code faster than we can understand it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Black Box" Pull Request
&lt;/h3&gt;

&lt;p&gt;Recently, I reviewed a Pull Request for a relatively simple data-fetching feature. The PR contained over 400 lines of highly abstracted, clever JavaScript. It worked perfectly. &lt;/p&gt;

&lt;p&gt;When I asked the developer to explain why they chose a specific caching strategy in the code, the answer was silence, followed by: &lt;em&gt;"That's just how the AI generated it, but it passes the tests."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the new reality. We have transitioned from &lt;strong&gt;Software Engineers&lt;/strong&gt; to &lt;strong&gt;Code Reviewers of AI&lt;/strong&gt;. The problem is, debugging code you didn't write is infinitely harder than debugging your own logic. When an AI generates a complex regular expression or a deeply nested state management hook, it lacks the context of your entire system's architecture. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Refactoring Nightmare
&lt;/h3&gt;

&lt;p&gt;Code is read 10x more than it is written. &lt;/p&gt;

&lt;p&gt;When you use AI to blast out 1,000 lines of code on a Friday, you are borrowing time from the future. Six months from now, when a weird edge-case bug appears in production, the AI won't be the one paged at 2 AM. You will be. &lt;/p&gt;

&lt;p&gt;And trying to untangle a dense, AI-generated spaghetti module that you never truly understood in the first place is a special kind of developer hell. I've found myself completely deleting AI-generated components and rewriting them from scratch just so I could actually understand the control flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Death of the Junior Developer Phase
&lt;/h3&gt;

&lt;p&gt;My biggest fear is for junior developers. The struggle of staring at a blank screen, reading the documentation, writing bad code, and refactoring it is exactly how you build the mental models required to become a Senior Developer.&lt;/p&gt;

&lt;p&gt;If juniors are just prompting an AI to solve their algorithmic challenges, they are skipping the most crucial phase of their career: learning &lt;em&gt;how&lt;/em&gt; to think about systems. We are building a generation of developers who know how to prompt, but don't know how to architect.&lt;/p&gt;

&lt;h3&gt;
  
  
  How We Fix This
&lt;/h3&gt;

&lt;p&gt;I'm not advocating we go back to the stone age and uninstall our AI assistants. But we desperately need a culture shift in how we treat AI code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The "Explain it to me" Rule:&lt;/strong&gt; If you submit an AI-generated PR, you must be able to explain every single line of it as if you wrote it yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI is a Junior Pair-Programmer:&lt;/strong&gt; Treat AI like an eager, fast-typing junior developer who often hallucinates. Never blindly trust its output without architectural oversight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write Tests First:&lt;/strong&gt; If you let AI write both the code and the tests simultaneously, you are just verifying that the AI agrees with itself. Write your test assertions yourself, then let the AI implement the logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The future of programming isn't typing. It's reading, reviewing, and architecting. &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What is your experience right now?&lt;/strong&gt; Are you finding that AI-generated code is harder to maintain, or have you found a workflow that keeps your codebase clean? Let’s debate in the comments!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Best Sites for SaaS Deals: How I Cut My Tech Stack Costs by 60%</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Mon, 31 Aug 2026 16:45:11 +0000</pubDate>
      <link>https://dev.to/liammartin/best-sites-for-saas-deals-how-i-cut-my-tech-stack-costs-by-60-3l12</link>
      <guid>https://dev.to/liammartin/best-sites-for-saas-deals-how-i-cut-my-tech-stack-costs-by-60-3l12</guid>
      <description>&lt;p&gt;A few months ago, I was looking at the monthly burn rate for my side projects. Between Postgres databases, authentication providers, email delivery APIs, error tracking, and a handful of marketing tools, I realized I was spending way more on subscriptions than my projects were actually generating.&lt;/p&gt;

&lt;p&gt;I used to just sign up, put in my credit card, and accept the retail price. That was a mistake. &lt;/p&gt;

&lt;p&gt;After spending way too much time researching how to optimize my infrastructure costs, I realized there's a whole ecosystem of websites designed to get builders and solo devs enterprise-grade tools without the enterprise price tag. The combination of these sites has created this nice feedback loop where I can test new tools quickly without destroying my budget.&lt;/p&gt;

&lt;p&gt;If you are looking for the best sites for SaaS deals to optimize your own stack, here is the exact list of resources I actually use and what I use them for.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Daily Cost-Optimization Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Carmen Tune (For the serious B2B stuff)
&lt;/h3&gt;

&lt;p&gt;When I need something heavy for client work or agency ops, this is my first stop. The site skips the random beta apps and focuses strictly on established, production-ready tools. I like it because I don't have to sift through endless noise; the deals are negotiated directly with tech companies, so they rarely show up on standard deal aggregators.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Secret (For cloud infrastructure)
&lt;/h3&gt;

&lt;p&gt;If you're bootstrapping an MVP, this one is basically mandatory. Secret is where I go to extend my operating runway. I used this platform to grab AWS credits and waive some Stripe processing fees when I was launching my last project. It’s essentially a launchpad for acquiring top-shelf architecture on a lean budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. NachoNacho (For subscription control)
&lt;/h3&gt;

&lt;p&gt;This solves a very specific engineering headache for me: shadow IT and forgotten trials. It’s part software marketplace, part virtual credit card manager. When I want to test a new API, I generate a unique virtual card here, set a hard spending limit, and get up to 30% cashback. If I forget to cancel a trial, the card simply declines. Total peace of mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Dealify (For the growth phase)
&lt;/h3&gt;

&lt;p&gt;I'm a developer, not a marketer. So when I need to figure out customer acquisition, SEO, or lead generation, I look here. Dealify ignores generic office utilities and focuses purely on software that drives traffic and optimizes conversions. &lt;/p&gt;

&lt;h3&gt;
  
  
  5. RocketHub (For direct founder access)
&lt;/h3&gt;

&lt;p&gt;I like this one because of the QA sections. Before I buy a lifetime deal, I can literally talk to the developer who built the tool and ask them about their rate limits, webhooks, and roadmap. They vet the founders carefully, which means you rarely end up with an abandoned app a year later.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. FounderPass (For mainstream defaults)
&lt;/h3&gt;

&lt;p&gt;This operates more like a private club for mainstream industry standards. Rather than hunting for new apps, I use FounderPass to lower the baseline overhead on tools I already know I’m going to use, like cloud hosting or standard workspace suites.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Freelance Stack (For solo operations)
&lt;/h3&gt;

&lt;p&gt;Most platforms assume you have VC funding and a team of twenty. This one assumes you are a solo operator working from your laptop. It’s full of lightweight invoicing, client portals, and time-tracking utilities without the enterprise-level complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. StackSocial (For utility bundles)
&lt;/h3&gt;

&lt;p&gt;It’s been around forever. Honestly, there’s a lot of noise here, but it’s still a dependable source when I need to grab a cheap VPN, a cybersecurity tool, or a bundled utility license for a random weekend project.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. SaaSPirate (The final check)
&lt;/h3&gt;

&lt;p&gt;When I already know what tool I want, I search for it here before putting in my credit card. It’s a comprehensive aggregator that indexes active discounts and promotional campaigns across the entire web. It saves me from opening ten different tabs to check for active coupons.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "I Can Build That Myself" Trap
&lt;/h2&gt;

&lt;p&gt;When developers look at a new tool, our first thought is usually that we can just build a minimal version ourselves to save money. I've definitely been there. I've lost weeks of development time coding internal utilities just to avoid a monthly fee.&lt;/p&gt;

&lt;p&gt;At the same time, paying standard retail prices makes zero sense when you are a solo developer.&lt;/p&gt;

&lt;p&gt;That is the real reason I use these deal sites. You grab a deal, plug it in, and get back to writing code. It stops you from wasting time building peripheral tools, and it keeps your monthly expenses low.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Will I Actually Use This?" Test
&lt;/h2&gt;

&lt;p&gt;Buying a tool at an 80% discount is still a waste of money if it just sits in your bookmarks. Before I migrate any core business process to a new platform, I run it through a strict mental model. &lt;/p&gt;

&lt;p&gt;I actually keep this simple JSON snippet in my notes app to remind myself not to buy shiny objects:&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;"evaluation_rules"&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;"rule_1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does it solve an active bottleneck today? (No theoretical future problems allowed)."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rule_2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does it have a clean API and webhook support for my current stack?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rule_3"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Can I export my data (CSV/JSON) immediately if the startup dies?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rule_4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Are the usage limits generous enough to handle my projected growth?"&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;If it fails any of those rules, I don't buy it, no matter how good the deal is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just Ship It
&lt;/h2&gt;

&lt;p&gt;Your tools don't define your success, but overpaying for them can definitely kill your runway. Pick the tools that feel right, optimize your costs where you can, and then stop thinking about it so you can get back to writing code.&lt;/p&gt;

&lt;p&gt;What is the one subscription in your stack that you pay for every month and absolutely don't regret? I'm always curious to see what other devs are using. Let me know in the comments!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>startup</category>
    </item>
    <item>
      <title>How to Parse and Filter Large JSON Files in Python</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Mon, 31 Aug 2026 16:01:38 +0000</pubDate>
      <link>https://dev.to/liammartin/how-to-parse-and-filter-large-json-files-in-python-109o</link>
      <guid>https://dev.to/liammartin/how-to-parse-and-filter-large-json-files-in-python-109o</guid>
      <description>&lt;p&gt;Working with JSON data is a daily task for most backend developers. However, when you start dealing with massive JSON files (think gigabytes of data from API dumps or system logs), using the standard &lt;code&gt;json.load()&lt;/code&gt; method can quickly consume all your available RAM and crash your application.&lt;/p&gt;

&lt;p&gt;Recently, I had to process a 4GB JSON file containing thousands of nested records. Instead of loading the entire file into memory, I used Python generators to parse and filter the data efficiently. &lt;/p&gt;

&lt;p&gt;Here is a quick breakdown of how to handle large JSON datasets without hitting memory limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Memory Problem
&lt;/h2&gt;

&lt;p&gt;Normally, parsing a JSON file in Python looks like this:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_bad_way&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# This loads the entire file into RAM at once
&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;json&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="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If file_path points to a file that is larger than your available memory, your script will throw a MemoryError and terminate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Generator Solution
&lt;/h2&gt;

&lt;p&gt;To process the data efficiently, we can read the file line by line (assuming it's a JSON Lines format, or .jl), parse each line individually, and yield the result. This keeps our memory footprint extremely low, regardless of the file size.&lt;/p&gt;

&lt;p&gt;Here is the script I use for iterative parsing and filtering:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_large_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter_keyword&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Reads a large JSON file line by line and yields records 
    that contain the specified filter_keyword.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Parse the single line into a Python dictionary
&lt;/span&gt;                &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

                &lt;span class="c1"&gt;# Apply your filtering logic here
&lt;/span&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;filter_keyword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;FileNotFoundError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;Error: The file &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; was not found.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: Invalid JSON format encountered.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;DATA_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system_logs.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Extracting active records...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# The generator only processes one record at a time
&lt;/span&gt;    &lt;span class="n"&gt;active_records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_large_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATA_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filter_keyword&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Iterate through the filtered results
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;valid_record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;active_records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;Found record ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;valid_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;By using the yield keyword, process_large_json becomes a generator. It pauses execution, returns the current record to the for loop, and waits to be called again before reading the next line from the file.&lt;/p&gt;

&lt;p&gt;The memory consumption remains flat because Python only holds a single string (the current line) and a single dictionary (the parsed record) in memory at any given time.&lt;/p&gt;

&lt;p&gt;How do you usually handle massive datasets in your backend applications? Do you prefer Python generators or do you rely on external tools like jq? Let me know your workflow in the comments!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Why We Killed the Per-User Subscription: The Shift to Value-Led SaaS Pricing</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Thu, 27 Aug 2026 20:12:39 +0000</pubDate>
      <link>https://dev.to/liammartin/why-we-killed-the-per-user-subscription-the-shift-to-value-led-saas-pricing-oin</link>
      <guid>https://dev.to/liammartin/why-we-killed-the-per-user-subscription-the-shift-to-value-led-saas-pricing-oin</guid>
      <description>&lt;p&gt;Two years ago, during an otherwise ordinary quarterly review, our head of customer success pulled up an account screen that made the room go silent.&lt;/p&gt;

&lt;p&gt;One of our enterprise clients—a fast-growing logistics company with over 300 employees—had exactly three paid seats on our platform. Those three seats, however, were running nearly round-the-clock automations, pinging our database hundreds of thousands of times a day, and driving about $40,000 in monthly operational value for their business.&lt;/p&gt;

&lt;p&gt;They were paying us $147 a month.&lt;/p&gt;

&lt;p&gt;When we looked closer, the reality was even more humiliating: the team had created a shared calendar invite containing the master login credentials. Whenever someone needed to pull a report or run a workflow, they all logged into the same master admin account.&lt;/p&gt;

&lt;p&gt;We were furious at first. We talked about implementing IP restrictions, forcing single sign-on (SSO) locks, and adding device limits to stop password sharing. But as the frustration settled, an uncomfortable truth emerged: our customers weren’t acting maliciously. They were simply responding rationally to a broken economic model.&lt;/p&gt;

&lt;p&gt;For two decades, the per-seat pricing model was the golden goose of B2B SaaS. Today, it is quietly strangling product adoption, misaligning incentives, and leaving massive revenue on the table. Here is why we abandoned user-based billing, what we built instead, and how shifting to a value-first pricing strategy completely transformed our Net Revenue Retention (NRR).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fatal Flaw of Per-Seat SaaS Pricing
&lt;/h2&gt;

&lt;p&gt;The per-user subscription model was popularized by early cloud giants like Salesforce. In the early 2000s, replacing bulky on-premise licenses with a predictable monthly fee per human user sitting in a cubicle was revolutionary.&lt;/p&gt;

&lt;p&gt;Today, that same model creates a direct conflict between your revenue goals and your user's desire to collaborate.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Traditional Per-Seat Dynamics:&lt;br&gt;
More Team Members Added  --&amp;gt;  Software Bill Increases&lt;br&gt;
Result: Managers restrict invites, share logins, and limit tool adoption.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you charge per user seat, you penalize your customers for spreading your tool across their organization. Instead of encouraging organic product virality, you force team leads to become internal gatekeepers who ask: &lt;em&gt;"Does Sarah in marketing really need access, or can someone just export a CSV for her once a week?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every time a customer decides not to invite a colleague to save $49 a month, your software becomes that much easier to churn out of when budget cuts arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Tsunami: Why Seat-Based Billing Cannot Survive
&lt;/h2&gt;

&lt;p&gt;If product virality issues weren't enough to kill per-seat pricing, modern generative AI and workflow automation have delivered the final blow.&lt;/p&gt;

&lt;p&gt;Consider what happens when software transitions from being a tool (which helps a human work faster) to an agent (which does the work autonomously).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Old Paradigm:&lt;/strong&gt; A support team uses a SaaS platform to handle tickets. As the company grows, they hire 10 more agents and buy 10 more seats. SaaS revenue scales linearly with customer headcount.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The New Paradigm:&lt;/strong&gt; A company implements autonomous AI agents inside the SaaS platform. The software resolves 70% of inbound tickets without human intervention. The company downsizes their tier to 3 human supervisors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under a per-seat model, the SaaS vendor that just delivered 5x more operational efficiency gets rewarded with a 70% drop in software revenue.&lt;/p&gt;

&lt;p&gt;Tying software value to human headcount in an era where software exists to reduce human headcount is an unsustainable business strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Learned Auditing Our Churn Data
&lt;/h2&gt;

&lt;p&gt;Before changing our pricing architecture, we audited 14 months of churn and downgrade surveys. The data exposed three distinct failure patterns in our seat-based model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The "Zombie Seat" Backlash:&lt;/strong&gt; During economic downturns, CFOs ran software audits. Finding 20 unused seats on an annual contract generated immediate resentment, leading to aggressive downgrades or outright cancellations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Collaboration Ceiling:&lt;/strong&gt; Accounts with 1 to 3 users had an annual churn rate of 34%. Accounts with 8 or more users churned at just 4%. Per-seat pricing was directly discouraging the very behavior that prevented churn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value Disconnect:&lt;/strong&gt; Small agencies handling massive client volumes were getting enterprise-grade value for entry-tier pricing, while large corporate departments with light usage felt gouged.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparing Modern SaaS Pricing Frameworks
&lt;/h2&gt;

&lt;p&gt;Transitioning away from pure seat-based tiers requires matching your pricing metric to how your product actually delivers value.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Pricing Model&lt;/th&gt;
      &lt;th&gt;Best Suited For&lt;/th&gt;
      &lt;th&gt;Primary Advantage&lt;/th&gt;
      &lt;th&gt;Main Risk&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Per-Seat (Legacy)&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Core workspace tools where every employee actively works daily (e.g., Slack, Google Workspace).&lt;/td&gt;
      &lt;td&gt;Highly predictable MRR for both vendor and buyer.&lt;/td&gt;
      &lt;td&gt;Encourages password sharing; penalizes organizational adoption.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Pure Usage / Consumption&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Developer infrastructure, compute, API integrations (e.g., Snowflake, Twilio, AWS).&lt;/td&gt;
      &lt;td&gt;Zero barrier to entry; scales directly with customer infrastructure.&lt;/td&gt;
      &lt;td&gt;Unpredictable monthly bills can scare enterprise procurement teams.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Outcome-Based&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Workflow automation, AI agents, revenue-generation tools (e.g., resolving tickets, generating pipeline).&lt;/td&gt;
      &lt;td&gt;Perfect alignment between customer ROI and software cost.&lt;/td&gt;
      &lt;td&gt;Difficult to define and attribute concrete "outcomes" across complex edge cases.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Hybrid (Platform Fee + Value Metric)&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Most B2B SaaS platforms combining a workspace with backend automation.&lt;/td&gt;
      &lt;td&gt;Predictable baseline revenue with uncapped upside as customer usage grows.&lt;/td&gt;
      &lt;td&gt;Requires clear in-app dashboards to track usage thresholds.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Hybrid Model: What We Built
&lt;/h2&gt;

&lt;p&gt;We realized that going 100% consumption-based would introduce too much revenue volatility for our finance team and too much billing anxiety for our customers.&lt;/p&gt;

&lt;p&gt;Instead, we designed a &lt;strong&gt;Hybrid Value Model&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unlimited Free Viewer &amp;amp; Collaborator Seats:&lt;/strong&gt; We made user seats practically free. Anyone inside an organization can view dashboards, leave comments, and review reports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictable Base Platform Tiers:&lt;/strong&gt; A fixed monthly platform fee (Starter, Growth, Scale) that covers core infrastructure, security compliance, and priority support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value-Metric Billing (The Growth Engine):&lt;/strong&gt; We identified the single metric most correlated with our customers' business success—in our case, Active Monitored Workflows. Customers buy capacity blocks for these workflows.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Our New Pricing Formula:&lt;br&gt;
Total Cost = Base Platform Fee + (Active Workflow Blocks Consumed)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The psychological shift was immediate. Managers stopped rationing logins. They invited their entire engineering, product, and operations teams on day one.&lt;/p&gt;

&lt;p&gt;Within six months, our average user count per account jumped from 3.8 to 14.2. More importantly, when those 14 people interacted with our product weekly, our software became deeply embedded across departments, cutting our gross churn in half.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Audit Your SaaS Pricing Metric
&lt;/h2&gt;

&lt;p&gt;If you suspect your current pricing structure is holding back your product-led growth, run through these diagnostic steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identify Your "Aha!" Metric:&lt;/strong&gt; What action inside your product directly correlates with your customer making or saving money? (e.g., emails sent, transactions processed, storage utilized, candidates screened).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the Seat-to-Value Ratio:&lt;/strong&gt; Calculate the revenue generated per active daily user across your top 10% and bottom 10% of accounts. If the difference is massive, your pricing is disconnected from reality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Your External Tooling Costs:&lt;/strong&gt; Before reworking your internal pricing, look at how much you're about to spend on incoming software subscriptions. When we were expanding our stack, checking discounts directly through &lt;a href="https://carmentune.com/en/" rel="noopener noreferrer"&gt;Carmen Tune&lt;/a&gt; helped us find the best software deals without paying full retail price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Survey Cancelled Accounts for Friction:&lt;/strong&gt; Look for keywords like "paying for people who barely log in" or "couldn't justify the cost for the whole department."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a Grandfathered Pilot:&lt;/strong&gt; Never force existing customers into a new pricing structure overnight. Pick a cohort of 50 new inbound signups and test a value-metric model against your control group to measure conversion and expansion rates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The era of charging companies simply for looking at a screen is coming to an end. By aligning your revenue with the tangible business value you create—rather than the number of email addresses in your database—you remove friction for your users and unlock durable, expansion-driven growth for your business.&lt;/p&gt;

&lt;p&gt;How is your team handling the balance between predictability and usage-based expansion in your current pricing model?&lt;/p&gt;

</description>
      <category>product</category>
      <category>saas</category>
      <category>startup</category>
    </item>
    <item>
      <title>The Great SaaS Pivot: From Hypergrowth to Sustainable Profitability</title>
      <dc:creator>Liam Martin</dc:creator>
      <pubDate>Thu, 27 Aug 2026 19:42:30 +0000</pubDate>
      <link>https://dev.to/liammartin/the-great-saas-pivot-from-hypergrowth-to-sustainable-profitability-ab6</link>
      <guid>https://dev.to/liammartin/the-great-saas-pivot-from-hypergrowth-to-sustainable-profitability-ab6</guid>
      <description>&lt;p&gt;The software-as-a-service (SaaS) industry is undergoing its most significant transformation since the widespread adoption of cloud computing. The prolonged era of cheap capital and "growth at all costs" has abruptly ended, replaced by a strict mandate from investors and boards for sustainable profitability, efficient customer acquisition, and undeniable value creation.&lt;/p&gt;

&lt;p&gt;This macro-economic shift has forced SaaS founders and executives to rewrite their operational playbooks. Startups and enterprise giants alike are no longer rewarded solely for adding top-line revenue if that revenue comes with exorbitant customer acquisition costs and high churn rates. Instead, the industry is witnessing a maturation process where business fundamentals finally matter as much as technological innovation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Death of "Growth at All Costs"
&lt;/h2&gt;

&lt;p&gt;For over a decade, the Zero Interest-Rate Policy (ZIRP) environment allowed SaaS companies to operate with heavy losses, provided they could show staggering year-over-year revenue growth. Venture capital flowed freely, subsidizing massive marketing budgets, oversized sales teams, and aggressive market expansions. The prevailing logic dictated that capturing market share early would inevitably lead to profitable monopolies later.&lt;/p&gt;

&lt;p&gt;That paradigm has fractured. Today, the defining metric for a healthy SaaS business is the "Rule of 40." This principle states that a software company’s combined growth rate and profit margin should exceed 40%. A company growing at 40% with a 0% profit margin is just as attractive as a mature company growing at 10% with a 30% profit margin. This equilibrium forces companies to balance their ambitions with their bank accounts.&lt;/p&gt;

&lt;p&gt;Founders are systematically auditing their operations, trimming bloated organizational charts, and focusing on metrics that demonstrate genuine operational leverage. The transition is painful for companies built entirely on the expectation of endless venture funding, but it is forging a much more resilient and fundamentally sound software ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Evolution of Key Performance Indicators
&lt;/h2&gt;

&lt;p&gt;As strategic priorities shift, the metrics used to measure success have evolved. The focus has moved from vanity metrics and sheer volume to efficiency and unit economics.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
            &lt;thead&gt;
                &lt;tr&gt;
                    &lt;th&gt;Metric Focus&lt;/th&gt;
                    &lt;th&gt;The "Hypergrowth" Era&lt;/th&gt;
                    &lt;th&gt;The "Profitability" Era&lt;/th&gt;
                    &lt;th&gt;Strategic Implication&lt;/th&gt;
                &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
                &lt;tr&gt;
                    &lt;td&gt;&lt;strong&gt;Primary Goal&lt;/strong&gt;&lt;/td&gt;
                    &lt;td&gt;Annual Recurring Revenue (ARR)&lt;/td&gt;
                    &lt;td&gt;Net Revenue Retention (NRR)&lt;/td&gt;
                    &lt;td&gt;Retaining and expanding existing accounts is more cost-effective than acquiring new ones.&lt;/td&gt;
                &lt;/tr&gt;
                &lt;tr&gt;
                    &lt;td&gt;&lt;strong&gt;Sales Efficiency&lt;/strong&gt;&lt;/td&gt;
                    &lt;td&gt;Total New Customers&lt;/td&gt;
                    &lt;td&gt;CAC Payback Period&lt;/td&gt;
                    &lt;td&gt;Capital efficiency dictates that marketing costs must be recouped in months, not years.&lt;/td&gt;
                &lt;/tr&gt;
                &lt;tr&gt;
                    &lt;td&gt;&lt;strong&gt;Cash Management&lt;/strong&gt;&lt;/td&gt;
                    &lt;td&gt;Cash Raised / Runway&lt;/td&gt;
                    &lt;td&gt;Free Cash Flow (FCF)&lt;/td&gt;
                    &lt;td&gt;Companies must demonstrate a path to self-sustainability without relying on external funding.&lt;/td&gt;
                &lt;/tr&gt;
                &lt;tr&gt;
                    &lt;td&gt;&lt;strong&gt;Product Value&lt;/strong&gt;&lt;/td&gt;
                    &lt;td&gt;Active Users (MAU/DAU)&lt;/td&gt;
                    &lt;td&gt;Gross Margin&lt;/td&gt;
                    &lt;td&gt;Delivering software must be inherently profitable on a per-unit basis, regardless of scale.&lt;/td&gt;
                &lt;/tr&gt;
            &lt;/tbody&gt;
        &lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Ascendance of Vertical SaaS
&lt;/h2&gt;

&lt;p&gt;As broad, horizontal SaaS categories (like general CRM, project management, and basic communication tools) become fiercely competitive and dominated by established titans, opportunity is shifting toward Vertical SaaS. These are software solutions purpose-built for specific, historically underserved industries—ranging from construction and agriculture to automotive repair and specialized retail.&lt;/p&gt;

&lt;p&gt;Horizontal SaaS attempts to be all things to all people, requiring extensive customization and prolonged onboarding to fit a specific business's workflow. Vertical SaaS, by contrast, speaks the customer's native language immediately. It arrives pre-configured with industry-specific compliance standards, workflows, and integrations.&lt;/p&gt;

&lt;p&gt;This specialization creates a massive competitive advantage. Vertical SaaS companies typically enjoy lower Customer Acquisition Costs (CAC) because their marketing is highly targeted, and they experience significantly lower churn rates because their software becomes deeply embedded in the daily operations of a niche business. When a platform manages a plumbing contractor's dispatching, inventory, payroll, and invoicing all in one place, ripping and replacing that software becomes nearly impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI as a Core Architecture, Not a Novelty
&lt;/h2&gt;

&lt;p&gt;Artificial Intelligence is no longer an experimental feature confined to a beta tab; it is becoming the foundational architecture upon which modern SaaS is built. In the past year, the industry has moved rapidly past the phase of merely slapping a generative AI chatbot onto an existing interface. Today, the most competitive platforms are integrating AI deeply into their operational mechanics.&lt;/p&gt;

&lt;p&gt;The true value of AI in SaaS lies in its ability to transition software from being a passive tool to an active participant in a user's workflow. Users no longer want a dashboard that merely displays data; they want an agentic system that analyzes the data, recommends a course of action, and executes it autonomously.&lt;/p&gt;

&lt;p&gt;Key AI implementation strategies driving the next generation of SaaS include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agentic Workflows:&lt;/strong&gt; Software that can chain together multiple steps—such as reading an incoming email, querying a database, drafting a customized response, and updating a CRM record—without human intervention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictive Analytics:&lt;/strong&gt; Moving beyond historical reporting to forecast trends, such as anticipating customer churn before it happens or predicting inventory shortages based on seasonal data and supply chain disruptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generative Customization:&lt;/strong&gt; Utilizing AI to instantly build customized dashboards, reports, and onboarding flows tailored to the specific role of the user logging in, drastically reducing time-to-value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Moats:&lt;/strong&gt; Companies with proprietary, highly specific datasets are leveraging them to train specialized AI models that out-perform generic foundational models within their specific domain.
##The Convergence of PLG and SLG
Product-Led Growth (PLG) was the darling strategy of the last half-decade. The idea was simple: build a product so intuitive and valuable that end-users would adopt it on their own, often via a freemium model, eventually forcing their organizations to pay for enterprise licenses. Slack, Zoom, and Figma championed this bottom-up approach, proving that you could bypass the traditional corporate IT procurement process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, relying exclusively on PLG has its limitations, particularly when attempting to secure six-figure enterprise contracts. To capture large-scale, organization-wide deployments, companies need the strategic maneuvering, relationship building, and security assurances that only a traditional enterprise sales team can provide.&lt;/p&gt;

&lt;p&gt;Enter the hybrid model: the convergence of Product-Led Growth and Sales-Led Growth (SLG). Modern SaaS companies are using PLG motions to generate highly qualified leads—letting individual contributors trial the software and find value. Once product usage within an organization reaches a critical mass, the enterprise sales team is triggered. They step in not to "sell" the product from scratch, but to negotiate volume discounts, navigate compliance requirements, and consolidate fragmented individual accounts into a unified, secure enterprise tier. This dual-pronged approach lowers acquisition costs at the bottom of the funnel while maximizing contract value at the top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retention and the Expansion Engine
&lt;/h2&gt;

&lt;p&gt;In a macroeconomic climate where acquiring new logos is increasingly expensive and difficult, the financial health of a SaaS company relies entirely on its existing customer base. Net Revenue Retention (NRR)—the percentage of recurring revenue retained from existing customers over a given period, including expansions and subtracting churn—is now heavily scrutinized. An NRR over 110% indicates that a company's revenue would continue to grow even if it never acquired another new customer.&lt;/p&gt;

&lt;p&gt;Achieving world-class NRR requires a fundamental shift in how companies view the post-sale journey. Customer Success is no longer a glorified support desk designed to put out fires; it is a vital revenue-generating function. Proactive customer success teams use telemetry data to monitor how deeply an account is adopting the software. If usage drops, they intervene before the renewal date. If usage spikes, they coordinate with sales to propose up-tiering the account or cross-selling new modules.&lt;/p&gt;

&lt;p&gt;SaaS pricing models are also evolving to support this retention-first mindset. Usage-based pricing (UBP), where customers pay based on consumption rather than fixed per-seat licenses, is gaining immense traction. This model perfectly aligns the software's cost with the value the customer receives, removing friction during initial adoption and allowing revenue to scale naturally as the customer grows.&lt;/p&gt;

&lt;p&gt;The software landscape is undeniably settling into a more mature, disciplined phase. The speculative frenzy has cleared, leaving behind a market that demands rigorous financial fundamentals, deeply specialized industry solutions, and profound technological integration. Companies that successfully navigate this pivot—balancing operational efficiency with the intelligent application of AI—will define the next, much more durable generation of enterprise software.&lt;/p&gt;

</description>
      <category>management</category>
      <category>saas</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
