<?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: Carlos José Castro Galante</title>
    <description>The latest articles on DEV Community by Carlos José Castro Galante (@carlosjcastrog).</description>
    <link>https://dev.to/carlosjcastrog</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%2F3850137%2F7c4306aa-d4a6-4d9d-9327-a2b882c9d13d.jpeg</url>
      <title>DEV Community: Carlos José Castro Galante</title>
      <link>https://dev.to/carlosjcastrog</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carlosjcastrog"/>
    <language>en</language>
    <item>
      <title>How I prepared for the AZ-900 and why it's the certification where you should start with Azure</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Tue, 29 Sep 2026 00:49:31 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/how-i-prepared-for-the-az-900-and-why-its-the-certification-where-you-should-start-with-azure-5e28</link>
      <guid>https://dev.to/carlosjcastrog/how-i-prepared-for-the-az-900-and-why-its-the-certification-where-you-should-start-with-azure-5e28</guid>
      <description>&lt;p&gt;In 2023, when I prepared for the AZ-900, I was studying Computer Science and had limited hands-on experience with cloud infrastructure. What stood out when studying was the exam's scope: it doesn't start with Azure services but with cloud computing fundamentals, which makes it useful for both technical and non-technical profiles.&lt;/p&gt;

&lt;p&gt;The AZ-900 (Microsoft Azure Fundamentals) is the introductory certification to the Azure ecosystem. It doesn't require prior tech experience, doesn't assume knowledge of programming, and doesn't expect you to have administered a server. It's designed for anyone who wants to understand how Microsoft's cloud works, both from the technical side and the organizational and financial one.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I got to this exam
&lt;/h2&gt;

&lt;p&gt;I got access through a program run by Microsoft and the &lt;a href="https://www.fundacionedison.org.ar/" rel="noopener noreferrer"&gt;Fundación Tomás Alva Edison&lt;/a&gt; in Mendoza, Argentina. The foundation has a partnership with Microsoft to bring these certifications to students and young people in the region.&lt;/p&gt;

&lt;p&gt;Today the exam is available through two channels. Most people book it through Pearson Vue, either in person at an authorized testing center or remotely from home through its OnVUE system. Students and educators can also book it through Certiport, which was the platform I used myself back in 2023 and remains active today as an option for that audience.&lt;/p&gt;

&lt;p&gt;I'm a Microsoft Student Ambassador, and part of what I do in that role is help other students work through this same path. That's why I put together a study plan on Microsoft Learn that organizes all the official modules in the order I'd recommend going through them: &lt;a href="https://learn.microsoft.com/plans/odp3ugt4okmdxk?source=docs&amp;amp;sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;AZ-900 Study Plan&lt;/a&gt;. You can start it right now while you read this.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the exam covers
&lt;/h2&gt;

&lt;p&gt;The AZ-900 has between 40 and 60 questions, requires a minimum score of 700 out of 1000 to pass, and you'll have 45 minutes to complete the assessment. There are no hands-on labs or case studies, and there's no penalty for wrong answers, so it's always worth answering even when you're not sure.&lt;/p&gt;

&lt;p&gt;The cost is $99 USD in the United States, though Microsoft adjusts pricing by country. In Argentina, the official price is $59 USD. Check the exact price for your region on the official scheduling page before booking.&lt;/p&gt;

&lt;p&gt;The content is organized into three major areas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cloud concepts
&lt;/h2&gt;

&lt;p&gt;The first area covers foundational concepts and is the most theoretical of the three. Before getting into Azure services specifically, the exam evaluates whether you understand what the cloud is and why it exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  The consumption model
&lt;/h3&gt;

&lt;p&gt;One of the most important concepts is the difference between CapEx and OpEx. CapEx (Capital Expenditure) is the upfront investment in physical infrastructure you own: you buy servers, set them up, maintain them, and that cost stays with you for the life of the equipment. OpEx (Operational Expenditure) is the operating expense model: you pay for what you use, when you use it, with no upfront hardware investment. The cloud operates under OpEx, and that shifts the financial equation entirely, especially for smaller organizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  The service models
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;IaaS (Infrastructure as a Service)&lt;/strong&gt; gives you the highest level of control. You rent the infrastructure (virtual servers, networking, storage) and you manage everything running on top: the operating system, middleware, applications, and data. A virtual machine on Azure is the clearest example. Think of it like renting an empty apartment where the building is already there but you decide everything that goes inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PaaS (Platform as a Service)&lt;/strong&gt; removes the responsibility for the operating system and underlying infrastructure. You get a ready-to-use environment for building and deploying applications. Azure App Service fits here: you push your code and the platform handles the rest. It's like working at a coworking space where the desk, internet, and infrastructure are already set up and you just show up to work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SaaS (Software as a Service)&lt;/strong&gt; is the model most people use every day without thinking about it. Microsoft 365, Teams, Gmail, and Notion are all SaaS. You use a finished application without thinking about any of the infrastructure underneath.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment models
&lt;/h3&gt;

&lt;p&gt;Public cloud is infrastructure the provider shares across multiple customers. Private cloud is infrastructure dedicated exclusively to a single organization. Hybrid cloud combines both, which is especially useful for organizations with legacy systems they can't fully migrate.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/plans/odp3ugt4okmdxk?source=docs&amp;amp;sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;study plan&lt;/a&gt; covers all of this with hands-on exercises where you can practice without needing your own subscription.&lt;/p&gt;




&lt;h2&gt;
  
  
  Azure architecture and services
&lt;/h2&gt;

&lt;p&gt;This is the most extensive area of the exam and the one that covers the greatest number of individual services. It's also the one that requires the most study time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regions and availability zones
&lt;/h3&gt;

&lt;p&gt;Azure is physically distributed across more than 70 regions worldwide. A region is a geographic area containing one or more datacenters. When you create a resource, you choose which region it lives in, and that choice affects latency, cost, and your organization's data residency requirements.&lt;/p&gt;

&lt;p&gt;Within many regions there are availability zones, which are physically separate datacenters within that same region, connected by low-latency networks. If one zone fails, the others keep running. Azure also has region pairs: each primary region has a secondary region that's geographically distant, for disaster recovery at a larger scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subscriptions and resource groups
&lt;/h3&gt;

&lt;p&gt;Every Azure resource lives inside a well-defined hierarchy. The top level is the Microsoft Entra ID tenant, which represents your organization. Within the tenant there are subscriptions, which are the billing unit. Within each subscription you create resource groups, which are logical containers that group related services for a project or application. Every Azure resource always belongs to exactly one resource group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compute
&lt;/h3&gt;

&lt;p&gt;Azure &lt;strong&gt;Virtual Machines&lt;/strong&gt; are the classic IaaS model: a full server in the cloud that you manage from the operating system upward. They're the most flexible option and also the one that carries the most management overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure App Service&lt;/strong&gt; is the PaaS platform for web apps and APIs. It supports .NET, Java, Node.js, Python, and PHP, and you don't manage the underlying server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Functions&lt;/strong&gt; is the serverless option. Functions execute only when something triggers them (an HTTP request, an event, a queue message) and you pay only for actual execution time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Kubernetes Service (AKS)&lt;/strong&gt; is the managed Kubernetes service for orchestrating containers at scale. &lt;strong&gt;Azure Container Instances&lt;/strong&gt; is the simpler option for running individual containers without needing a full cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Networking
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Virtual Network (VNet)&lt;/strong&gt; is the private network where your Azure resources live. You can segment it into subnets, control traffic with Network Security Groups (NSGs), and connect your on-premises network to Azure through VPN Gateway or Azure ExpressRoute, which provides a private dedicated connection that doesn't go through the public internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Load Balancer&lt;/strong&gt; distributes traffic across multiple instances to ensure availability. &lt;strong&gt;Azure Application Gateway&lt;/strong&gt; does something similar at the application layer and includes a Web Application Firewall. &lt;strong&gt;Azure CDN&lt;/strong&gt; brings static content closer to users by distributing it across points of presence around the world.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Azure Blob Storage&lt;/strong&gt; is Azure's object storage service, designed for unstructured data like files, images, videos, and backups. It has three access tiers based on how frequently data is needed: Hot for frequent access, Cool for data accessed less than once a month, and Archive for data that's rarely retrieved and where hours of retrieval latency is acceptable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Files&lt;/strong&gt; provides a shared file system compatible with the SMB protocol and accessible from Windows, Linux, and macOS. &lt;strong&gt;Azure Queue Storage&lt;/strong&gt; is a messaging service for decoupling components of an application.&lt;/p&gt;

&lt;p&gt;On the redundancy side, LRS replicates data three times within a single datacenter, ZRS replicates across availability zones, GRS replicates to a secondary region, and GZRS combines zone redundancy with geo-replication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identity and access
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Entra ID&lt;/strong&gt; (formerly Azure Active Directory) is Microsoft's cloud identity service. It manages authentication and authorization for Azure, Microsoft 365, and third-party applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Role-Based Access Control (RBAC)&lt;/strong&gt; lets you assign specific permissions to users, groups, or service identities over specific resources, following the principle of least privilege. The most common built-in roles are Owner, Contributor, and Reader.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-Factor Authentication (MFA)&lt;/strong&gt; adds a second verification layer beyond the password. &lt;strong&gt;Conditional Access&lt;/strong&gt; lets you define policies that evaluate the context of an access attempt, such as location, device, or application, before allowing or blocking it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Management and governance
&lt;/h2&gt;

&lt;p&gt;The third area covers how to manage, monitor, and control costs in Azure. It's the one most people underestimate during study and the one that carries real weight in the actual exam.&lt;/p&gt;

&lt;h3&gt;
  
  
  Management tools
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Azure Portal&lt;/strong&gt; is the main web interface for creating and managing resources. &lt;strong&gt;Azure CLI&lt;/strong&gt; and &lt;strong&gt;Azure PowerShell&lt;/strong&gt; are the command-line alternatives for those who prefer automation or scripting. &lt;strong&gt;Azure Cloud Shell&lt;/strong&gt; is a terminal available directly from the browser, with Azure CLI and PowerShell already configured, with no local installation needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Resource Manager (ARM)&lt;/strong&gt; is the layer that processes all resource creation, modification, and deletion requests, whether they come from the portal, the CLI, or an API. ARM templates and Bicep allow you to define infrastructure as code and deploy it repeatably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Arc&lt;/strong&gt; extends Azure's management capabilities to resources running outside of Azure, like servers in other clouds or in on-premises datacenters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Costs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Azure Cost Management&lt;/strong&gt; lets you see your detailed spending, set budgets, and configure automatic alerts when consumption approaches a defined limit. The &lt;strong&gt;Azure Pricing Calculator&lt;/strong&gt; helps estimate the cost of an architecture before deploying anything. The &lt;strong&gt;Total Cost of Ownership (TCO) Calculator&lt;/strong&gt; helps compare the cost of maintaining on-premises infrastructure against migrating to the cloud.&lt;/p&gt;

&lt;p&gt;Pay-as-you-go lets you pay for what you use with no prior commitments. &lt;strong&gt;Azure Reservations&lt;/strong&gt; let you reserve specific capacity for one or three years in exchange for discounts of up to 72% compared to the pay-as-you-go price.&lt;/p&gt;

&lt;h3&gt;
  
  
  Governance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Azure Policy&lt;/strong&gt; lets you define and enforce rules on resources within a subscription, for example requiring that all new resources have mandatory tags, or restricting which VM sizes can be created. &lt;strong&gt;Resource Locks&lt;/strong&gt; protect critical resources from accidental deletion or modification, even for accounts with administrator-level permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Azure Monitor&lt;/strong&gt; is the platform's central monitoring service. It collects metrics and logs from all your resources and lets you create alerts, dashboards, and analysis workbooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application Insights&lt;/strong&gt; is an Azure Monitor extension built specifically for applications. It tracks response times, exceptions, dependencies, and user behavior in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Service Health&lt;/strong&gt; reports on the current status of Azure services in each region and on any incidents or planned maintenance that might affect your resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Defender for Cloud&lt;/strong&gt; assesses the security posture of your resources and generates concrete recommendations for reducing risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to prepare
&lt;/h2&gt;

&lt;p&gt;Study time varies depending on your background. Without any prior tech experience, two to four weeks of consistent study is realistic. With a technical foundation already in place, under two weeks is very doable.&lt;/p&gt;

&lt;p&gt;Microsoft Learn has the official learning path completely free, with interactive modules and sandboxes where you can practice directly in Azure without needing your own subscription or a credit card. Microsoft also offers a free official practice assessment that lets you get familiar with the real exam format before the day you sit for it.&lt;/p&gt;

&lt;p&gt;The free Azure account includes $200 in credit to use within your first 30 days, plus permanent access to a set of always-free services. It's useful for experimenting beyond Learn's sandboxes.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/plans/odp3ugt4okmdxk?source=docs&amp;amp;sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;study plan I put together&lt;/a&gt; organizes the Microsoft Learn modules in the order I'd recommend working through them, grouped by exam area. You can complete each module at your own pace and track your progress as you go.&lt;/p&gt;




&lt;h2&gt;
  
  
  To close
&lt;/h2&gt;

&lt;p&gt;Once you pass the AZ-900, the natural next steps on the technical Azure path are the AZ-104 (Administrator) or the AZ-204 (Developer). On the AI side, the AI-900 is the equivalent introductory cert before moving to the AI-102 or AI-103.&lt;/p&gt;

&lt;p&gt;If you'd like to study alongside others, on October 15 we're organizing an event where we'll go through the most important exam topics together. Leave a comment or reach out directly if you're interested.&lt;/p&gt;

&lt;p&gt;The study plan is already there: &lt;a href="https://learn.microsoft.com/plans/odp3ugt4okmdxk?source=docs&amp;amp;sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;AZ-900 Study Plan on Microsoft Learn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>learning</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Azure Multicloud Interconnect, how Azure and AWS now connect without a middleman</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Tue, 22 Sep 2026 15:26:57 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/azure-multicloud-interconnect-how-azure-and-aws-now-connect-without-a-middleman-16jc</link>
      <guid>https://dev.to/carlosjcastrog/azure-multicloud-interconnect-how-azure-and-aws-now-connect-without-a-middleman-16jc</guid>
      <description>&lt;p&gt;A private connection between an Azure VNet and an AWS VPC has traditionally required assembling Azure ExpressRoute, AWS Direct Connect, a connectivity provider or colocation footprint, customer-managed routers, BGP sessions, and link-layer encryption, then owning the resiliency design and day-to-day operations across all of it. The result was typically slow to deliver, difficult to troubleshoot, and inconsistent in performance.&lt;/p&gt;

&lt;p&gt;On August 31, 2026, Microsoft and AWS jointly announced Azure Multicloud Interconnect, a co-engineered managed service that replaces that assembly with a single interconnect resource, turning the private path between clouds into something you provision rather than something you build.&lt;/p&gt;

&lt;p&gt;For the official overview and creation guide: &lt;a href="https://learn.microsoft.com/azure/multicloud-interconnect/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;What is Azure Multicloud Interconnect?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the service actually is
&lt;/h2&gt;

&lt;p&gt;Azure Multicloud Interconnect isn't a rebranding of ExpressRoute with a different interface but rather a new resource type that sits on top of ExpressRoute's infrastructure and abstracts everything on both sides of the connection. Under the covers, Microsoft and AWS coordinate the circuits, routing, and encryption on your behalf using the open API specification for network interoperability that AWS introduced at re:Invent 2025 and brought to general availability in April 2026. Microsoft adopted that specification for Azure.&lt;/p&gt;

&lt;p&gt;The physical design is quad-redundant with a 400G-class backbone and MACsec encryption at the hardware layer, which means the encryption happens at the link level without consuming host CPU resources on either end. Without any of that configuration on your part, you create an interconnect resource, exchange an activation key with the AWS side, and the managed path between your Azure Virtual Network and your AWS VPC is ready.&lt;/p&gt;

&lt;p&gt;The distinction from ExpressRoute comes down to what you're managing: with a traditional ExpressRoute circuit connected to AWS through a carrier, you're the systems integrator for the full stack, while with Azure Multicloud Interconnect, Microsoft and AWS own the underlying complexity and you consume the outcome.&lt;/p&gt;

&lt;p&gt;For a detailed look at how the service fits into the existing connectivity landscape: &lt;a href="https://learn.microsoft.com/azure/multicloud-interconnect/faq?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Multicloud Interconnect FAQ&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How provisioning works
&lt;/h2&gt;

&lt;p&gt;The setup process moves through four stages. The first is creating an ExpressRoute circuit and selecting the Azure Multicloud Interconnect port type rather than a carrier-based port. The second is creating the Interconnect resource itself, which generates the activation key that coordinates the connection between providers. The third is provider setup on the AWS side, where you redeem the activation key and select the Direct Connect Gateway that the connection terminates on. The fourth is connecting your Azure Virtual Networks through an ExpressRoute gateway so that workloads in your Azure VNet and workloads in your AWS VPC can communicate over the managed path.&lt;/p&gt;

&lt;p&gt;Both sides must use separate, non-overlapping IP address spaces, which is a standard requirement for any BGP-routed private connection.&lt;/p&gt;

&lt;p&gt;On the AWS side, the experience mirrors what AWS already provides for Google Cloud and Oracle Cloud Infrastructure through AWS Interconnect, so teams that have already connected AWS to another cloud provider using that service will find the workflow familiar.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the preview limits mean in practice
&lt;/h2&gt;

&lt;p&gt;The Azure preview launched with constraints worth understanding before building production plans on top of it.&lt;/p&gt;

&lt;p&gt;Throughput during preview is capped at 1 Gbps, while the Google Cloud pairing, which reached general availability in April 2026, already supports up to 100 Gbps. AWS confirmed that general availability for the Azure connection is expected to reach the same ceiling eventually, but the timeline isn't published. For workloads that move large volumes of data between clouds, whether for AI training pipelines, database replication, or large-scale analytics, 1 Gbps is a meaningful constraint. For control-plane traffic, API calls between microservices split across providers, or proof-of-concept deployments, it's sufficient.&lt;/p&gt;

&lt;p&gt;There's no SLA during preview, which is standard for preview services but matters for teams evaluating the service for anything that needs an uptime commitment from the provider.&lt;/p&gt;

&lt;p&gt;Regional availability in preview covers four Azure regions: Australia East, East US, Germany West Central, and West US, each paired with a corresponding AWS region: Asia Pacific (Sydney), US East (N. Virginia), Europe (Frankfurt), and US West (N. California). Cross-region connectivity isn't supported during preview, meaning an Azure VNet in East US connects to the AWS US East (N. Virginia) pair and can't route through a different region pair.&lt;/p&gt;

&lt;p&gt;The official source for current supported regions, bandwidth tiers, and SLA status: &lt;a href="https://azure.microsoft.com/products/multicloud-interconnect/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Multicloud Interconnect product page&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it differs from ExpressRoute
&lt;/h2&gt;

&lt;p&gt;The FAQ for the service makes the distinction explicit: choose Azure Multicloud Interconnect for managed connectivity between Azure and a supported cloud service provider, and choose ExpressRoute for on-premises, colocation, carrier-based connectivity, or Microsoft peering scenarios. The two aren't alternatives for the same use case because they're designed for different connectivity targets that happen to share the same underlying technology.&lt;/p&gt;

&lt;p&gt;An important implication is that existing ExpressRoute circuits don't automatically become Multicloud Interconnect resources, so if you're already connecting Azure and AWS through an ExpressRoute circuit over a carrier, the new service requires provisioning a new resource with the Azure Multicloud Interconnect port type, which is a separate circuit from what you may already be paying for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader context
&lt;/h2&gt;

&lt;p&gt;Azure Multicloud Interconnect launched as part of a pattern AWS established with the open interoperability specification it published at re:Invent 2025. Google Cloud was the first provider to adopt the specification and reached general availability with AWS in April 2026. Oracle Cloud Infrastructure entered preview in May and reached general availability in July, and Azure followed into preview in August.&lt;/p&gt;

&lt;p&gt;That sequence matters for teams comparing the maturity of each pairing. The Google Cloud and OCI connections through AWS Interconnect have already moved through preview and into production-grade service with published SLAs and higher bandwidth tiers. The Azure pairing is earlier in that lifecycle, but the underlying specification is the same and the GA trajectory follows the same path the other pairings already completed.&lt;/p&gt;

&lt;p&gt;For the full technical announcement from the Azure Networking team: &lt;a href="https://techcommunity.microsoft.com/blog/azurenetworkingblog/simpler-private-connectivity-between-azure-and-aws-with-azure-multicloud-interco/4550556?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Simpler, private connectivity between Azure and AWS with Azure Multicloud Interconnect&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/multicloud-interconnect/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;What is Azure Multicloud Interconnect? - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/multicloud-interconnect/faq?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Multicloud Interconnect FAQ - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/products/multicloud-interconnect/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Multicloud Interconnect product page - Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/blog/introducing-azure-multicloud-interconnect-for-aws/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Introducing Azure Multicloud Interconnect for AWS - Azure Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techcommunity.microsoft.com/blog/azurenetworkingblog/simpler-private-connectivity-between-azure-and-aws-with-azure-multicloud-interco/4550556?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Simpler, private connectivity between Azure and AWS - Azure Networking Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/expressroute/expressroute-introduction?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure ExpressRoute overview - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft and AWS documentation as of September 2026. Azure Multicloud Interconnect is in public preview and service capabilities including supported regions, bandwidth tiers, and SLA status are subject to change. Always verify current status at Microsoft Learn before building production architectures on preview features.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>aws</category>
      <category>cloud</category>
      <category>networking</category>
    </item>
    <item>
      <title>Azure Developer CLI, from zero to deployed in three commands</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Fri, 18 Sep 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/azure-developer-cli-from-zero-to-deployed-in-three-commands-549m</link>
      <guid>https://dev.to/carlosjcastrog/azure-developer-cli-from-zero-to-deployed-in-three-commands-549m</guid>
      <description>&lt;p&gt;Deploying an application to Azure by hand means provisioning resources in the portal, noting their connection strings, configuring environment variables, writing a deployment script, setting up a CI/CD pipeline, and repeating most of that process every time you start a new project or add a new environment. The Azure Developer CLI, known as azd, replaces that sequence with a small set of commands that handle provisioning, deployment, and environment management together instead of as separate concerns you have to coordinate manually.&lt;/p&gt;

&lt;p&gt;This post covers what azd actually does, how to use it with a Next.js project, the configuration file that makes everything work, the commands you will use daily, and the significant updates that shipped in 2026, including multi-language hooks in April, the azd tool command group in May, and the extension framework reaching general availability in August.&lt;/p&gt;

&lt;p&gt;For the official overview and installation guide: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Developer CLI overview on Microsoft Learn&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What azd does differently from the Azure CLI
&lt;/h2&gt;

&lt;p&gt;The Azure CLI (&lt;code&gt;az&lt;/code&gt;) is a general-purpose interface to Azure's API that gives you a command for every Azure operation without an opinion about how your project is structured or how to coordinate provisioning and deployment together. azd is opinionated. It assumes your project has infrastructure code, application code, and one or more environments, and it provides commands that work across all three at once.&lt;/p&gt;

&lt;p&gt;The workflow azd enables is: write your application, define your infrastructure with Bicep or Terraform, describe the mapping between them in &lt;code&gt;azure.yaml&lt;/code&gt;, and then use three commands for everything else. &lt;code&gt;azd init&lt;/code&gt; to set up the project, &lt;code&gt;azd provision&lt;/code&gt; to create Azure resources, and &lt;code&gt;azd deploy&lt;/code&gt; to push your code. &lt;code&gt;azd up&lt;/code&gt; does the last two in a single step. &lt;code&gt;azd down&lt;/code&gt; tears everything down when you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew tap azure/azd &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; brew &lt;span class="nb"&gt;install &lt;/span&gt;azd

&lt;span class="c"&gt;# Windows (winget)&lt;/span&gt;
winget &lt;span class="nb"&gt;install &lt;/span&gt;microsoft.azd

&lt;span class="c"&gt;# Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://aka.ms/install-azd.sh | bash

&lt;span class="c"&gt;# Verify&lt;/span&gt;
azd version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current version as of August 2026 is 1.32.0. azd updates frequently with monthly releases, so running &lt;code&gt;azd version&lt;/code&gt; after installation is worth doing to confirm you have a current build.&lt;/p&gt;

&lt;p&gt;To install azd for the first time or update your installation: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Install or update the Azure Developer CLI&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The azure.yaml file
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;azure.yaml&lt;/code&gt; file at the root of your project is what azd reads to understand how your application maps to Azure. A typical file for a Next.js app looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-nextjs-app&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-nextjs-app@0.0.1-beta&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;js&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appservice&lt;/span&gt;

&lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;preprovision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/setup-env.sh&lt;/span&gt;
  &lt;span class="na"&gt;postdeploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/verify-deployment.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; section maps each part of your application to an Azure host. The &lt;code&gt;hooks&lt;/code&gt; section runs scripts at specific points in the provisioning and deployment lifecycle. Starting in April 2026, hooks support Python, JavaScript, TypeScript, and .NET alongside the existing Bash and PowerShell options, with automatic dependency management so you don't have to install anything separately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;preprovision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pwsh&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/preflight.ps1&lt;/span&gt;
  &lt;span class="na"&gt;postprovision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/seed-database.py&lt;/span&gt;
  &lt;span class="na"&gt;predeploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;typescript&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/build-check.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the complete azure.yaml schema reference: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/azd-schema?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;azure.yaml schema documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Core commands
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;azd init&lt;/code&gt; sets up a new project either from a template in the gallery or from your existing codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Initialize from a template&lt;/span&gt;
azd init &lt;span class="nt"&gt;--template&lt;/span&gt; todo-nodejs-mongo

&lt;span class="c"&gt;# Initialize from the current directory&lt;/span&gt;
azd init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;azd provision&lt;/code&gt; creates the Azure resources defined in your Bicep or Terraform files without deploying any application code, which is useful when you want to inspect or validate infrastructure separately from your deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd provision

&lt;span class="c"&gt;# Override subscription and location without changing config&lt;/span&gt;
azd provision &lt;span class="nt"&gt;--subscription&lt;/span&gt; your-subscription-id &lt;span class="nt"&gt;--location&lt;/span&gt; eastus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--subscription&lt;/code&gt; and &lt;code&gt;--location&lt;/code&gt; flags arrived in February 2026 and make it straightforward to provision to different targets per environment without modifying &lt;code&gt;azure.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;azd deploy&lt;/code&gt; pushes your application code to the already-provisioned resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd deploy

&lt;span class="c"&gt;# Deploy a specific service only&lt;/span&gt;
azd deploy &lt;span class="nt"&gt;--service&lt;/span&gt; web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;azd up&lt;/code&gt; runs provision and deploy together, which is what you will use most of the time, and &lt;code&gt;azd down&lt;/code&gt; deletes all resources associated with the current environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd up

azd down

&lt;span class="c"&gt;# Delete without confirmation prompt&lt;/span&gt;
azd down &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nt"&gt;--purge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Environment management
&lt;/h2&gt;

&lt;p&gt;azd manages environments as named collections of configuration values that persist between commands, where each environment stores the Azure subscription, location, and any variables your infrastructure needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create environments for different stages&lt;/span&gt;
azd &lt;span class="nb"&gt;env &lt;/span&gt;new staging
azd &lt;span class="nb"&gt;env &lt;/span&gt;new production

&lt;span class="c"&gt;# Switch between environments&lt;/span&gt;
azd &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="k"&gt;select &lt;/span&gt;staging
azd up

azd &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="k"&gt;select &lt;/span&gt;production
azd up

&lt;span class="c"&gt;# See all environments and their active status&lt;/span&gt;
azd &lt;span class="nb"&gt;env &lt;/span&gt;list

&lt;span class="c"&gt;# Get all values from the active environment&lt;/span&gt;
azd &lt;span class="nb"&gt;env &lt;/span&gt;get-values
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each environment maps to a separate set of Azure resources, so staging and production never share infrastructure even when they come from the same template.&lt;/p&gt;

&lt;h2&gt;
  
  
  The azd tool command group
&lt;/h2&gt;

&lt;p&gt;One of the more useful additions that shipped in May 2026 is &lt;code&gt;azd tool&lt;/code&gt;, a unified command group for discovering, installing, and managing Azure development tools directly from the CLI without hunting for separate install documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all available tools with their install status and version&lt;/span&gt;
azd tool list

&lt;span class="c"&gt;# Show details for a specific tool&lt;/span&gt;
azd tool show azure-functions-core-tools

&lt;span class="c"&gt;# Install a tool&lt;/span&gt;
azd tool &lt;span class="nb"&gt;install &lt;/span&gt;azure-functions-core-tools

&lt;span class="c"&gt;# Update a tool&lt;/span&gt;
azd tool update azure-functions-core-tools

&lt;span class="c"&gt;# Uninstall with a dry run first to see what would be removed&lt;/span&gt;
azd tool uninstall azure-functions-core-tools &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
azd tool uninstall azure-functions-core-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This replaced the pattern of going to separate documentation pages for each tool and running different package manager commands depending on the operating system. The August 2026 release added guidance for bulk removal and per-agent skill output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The extension framework
&lt;/h2&gt;

&lt;p&gt;The azd extension framework reached general availability in August 2026 and extensions can now be installed directly from an HTTPS URL without registering a source first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install an extension from the official registry&lt;/span&gt;
azd extension &lt;span class="nb"&gt;install &lt;/span&gt;microsoft.azd.extensions.foundry

&lt;span class="c"&gt;# Install directly from a URL&lt;/span&gt;
azd extension &lt;span class="nb"&gt;install &lt;/span&gt;https://your-server.com/my-extension.tar.gz

&lt;span class="c"&gt;# List installed extensions&lt;/span&gt;
azd extension list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Microsoft ships two extensions worth knowing. The azd AI agent extension for Microsoft Foundry integrates azd workflows with Foundry projects, and the GitHub Copilot coding agent configuration extension sets up the configuration Copilot needs to understand and work with your azd project. Both are available through the official extension registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD pipeline integration
&lt;/h2&gt;

&lt;p&gt;azd can configure a GitHub Actions or Azure DevOps pipeline for your project with a single command that creates the workflow files, sets up the necessary federated credentials, and configures the repository secrets azd needs to run in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd pipeline config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting in August 2026, federated authentication support for Azure DevOps is documented and supported, replacing the older approach that required managing client secrets manually.&lt;/p&gt;

&lt;p&gt;For CI/CD pipeline setup: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/configure-devops-pipeline?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Explore Azure Developer CLI support for CI/CD pipelines&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Templates
&lt;/h2&gt;

&lt;p&gt;The template gallery is the fastest way to get a complete azd-ready project structure without writing Bicep from scratch, where each template includes the &lt;code&gt;azure.yaml&lt;/code&gt;, the infrastructure definitions, and often the application scaffolding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Browse and select a template interactively&lt;/span&gt;
azd init

&lt;span class="c"&gt;# Initialize directly from a known template&lt;/span&gt;
azd init &lt;span class="nt"&gt;--template&lt;/span&gt; azure-samples/todo-python-mongo-swa-func
azd init &lt;span class="nt"&gt;--template&lt;/span&gt; azure-samples/azure-search-openai-demo
azd init &lt;span class="nt"&gt;--template&lt;/span&gt; azure-samples/chat-with-your-data-solution-accelerator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI App Templates gallery is the relevant starting point for anything that involves Azure OpenAI, Azure AI Search, or Microsoft Foundry: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/azd-templates?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;azd templates and the template gallery&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Developer CLI overview - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Install or update the Azure Developer CLI - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/azd-schema?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;azure.yaml schema reference - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/azd-templates?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;azd templates and the template gallery - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/configure-devops-pipeline?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;CI/CD pipeline support with azd - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/azure-sdk/azure-developer-cli-azd-august-2026/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Developer CLI August 2026 release - Azure SDK Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft documentation and verified sources as of September 2026. azd ships monthly releases and some commands or flags may have changed. Always verify current behavior at Microsoft Learn before implementing in production workflows.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Foundry IQ, the knowledge layer that stops every agent from rebuilding its own RAG</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Tue, 15 Sep 2026 16:30:00 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/foundry-iq-the-knowledge-layer-that-stops-every-agent-from-rebuilding-its-own-rag-1h46</link>
      <guid>https://dev.to/carlosjcastrog/foundry-iq-the-knowledge-layer-that-stops-every-agent-from-rebuilding-its-own-rag-1h46</guid>
      <description>&lt;p&gt;Every team that builds more than one AI agent eventually hits the same problem. The first agent gets its own retrieval pipeline with data connectors, chunking logic, embeddings, routing, and permission enforcement. The second agent needs to answer from the same documents, so the pipeline gets duplicated. By the third agent, there's a tangle of fragmented, siloed pipelines that are expensive to maintain and inconsistent in quality, with each one rebuilding the same foundation from scratch.&lt;/p&gt;

&lt;p&gt;Foundry IQ is Microsoft's answer to that problem. Rather than adding more tools to the pipeline, it moves the retrieval layer out of each agent and into a shared, managed knowledge layer that multiple agents can query through a single API. The agents stop owning retrieval; they delegate it.&lt;/p&gt;

&lt;p&gt;Before going further, it's worth clarifying a naming detail that causes confusion. Microsoft ships three distinct IQ products under the Microsoft Foundry umbrella. &lt;strong&gt;Foundry IQ&lt;/strong&gt; handles enterprise documents and knowledge sources, which is what this article is about. &lt;strong&gt;Work IQ&lt;/strong&gt; is a contextual intelligence layer for Microsoft 365 that captures signals from documents, meetings, chats, and workflows. &lt;strong&gt;Fabric IQ&lt;/strong&gt; models business data in OneLake and Power BI so agents can reason over analytics. They're separate products with different semantics, freshness characteristics, and authorization boundaries. A policy assistant may need only Foundry IQ, while a sales-planning agent might combine Work IQ, Fabric IQ, and web context. This article stays focused on Foundry IQ.&lt;/p&gt;

&lt;p&gt;For the official overview of what Foundry IQ is and how it fits into Microsoft Foundry, the Microsoft Learn documentation is the authoritative source: &lt;a href="https://learn.microsoft.com/azure/foundry/agents/concepts/what-is-foundry-iq?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;What is Foundry IQ?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Foundry IQ is built on
&lt;/h2&gt;

&lt;p&gt;Foundry IQ is built directly on Azure AI Search. This is the part most introductions skip over, and it matters for architecture decisions. A Foundry IQ knowledge base is not a separate service sitting next to Azure AI Search. It's a top-level object that lives on your Azure AI Search service and orchestrates agentic retrieval. When you create a knowledge base, you're creating an object in Azure AI Search that defines which knowledge sources to query and how retrieval behaves at query time.&lt;/p&gt;

&lt;p&gt;The practical consequence is that you don't choose between Foundry IQ and Azure AI Search. Foundry IQ is the new interface for building agents on top of Azure AI Search, wrapping the underlying search capabilities in a managed, permission-aware, multi-agent-ready layer.&lt;/p&gt;

&lt;p&gt;To explore the agentic retrieval capabilities that underpin knowledge bases, the Azure AI Search documentation covers the model in detail: &lt;a href="https://learn.microsoft.com/azure/search/agentic-retrieval-how-to-create-knowledge-base?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Create a knowledge base in Azure AI Search&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowledge sources and knowledge bases
&lt;/h2&gt;

&lt;p&gt;Two concepts are foundational to how Foundry IQ is structured. A &lt;strong&gt;knowledge source&lt;/strong&gt; is a connection to data, whether that's an Azure Blob Storage container, a SharePoint site, an OneLake lakehouse, an existing Azure AI Search index, or a web source accessed through the public internet or an MCP server. A &lt;strong&gt;knowledge base&lt;/strong&gt; bundles one or more knowledge sources, up to ten per knowledge base, plus the parameters that control retrieval behavior. The knowledge base is what agents actually query.&lt;/p&gt;

&lt;p&gt;The distinction matters because multiple agents can share the same knowledge base. An HR assistant and a policy assistant might both need access to the same document library. With a traditional RAG approach, each agent builds its own pipeline to that library. With Foundry IQ, both agents point at the same knowledge base and the retrieval layer handles routing, permissions, and answer synthesis once.&lt;/p&gt;

&lt;p&gt;Knowledge sources come in two classes. Indexed sources are ingested into a search index on your Azure AI Search service, where the content is chunked, vectorized, and kept fresh through an indexer schedule. This class includes existing Azure AI Search indexes, Azure Blob Storage containers, and OneLake lakehouses, and these are generally available in the &lt;code&gt;2026-04-01&lt;/code&gt; REST API. Federated sources are queried at runtime without pre-ingestion, including web sources and MCP servers, and these remain in preview in the &lt;code&gt;2026-05-01-preview&lt;/code&gt; and &lt;code&gt;2026-08-01-preview&lt;/code&gt; APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  GA versus preview: what this means for production
&lt;/h2&gt;

&lt;p&gt;Some of the most useful Foundry IQ features are still in preview, and the distinction matters when deciding what to build on. The &lt;code&gt;2026-04-01&lt;/code&gt; REST API provides a production-endorsed path with general availability for knowledge bases and core indexed knowledge sources. Features that remain in preview as of the time of writing include answer synthesis using an LLM for query planning and response generation, configurable reasoning effort tiers, document-level permissions, multi-turn conversational retrieval, and most of the portal-based management experience. The Azure portal and Microsoft Foundry portal continue to provide preview-only access to all agentic retrieval features regardless of which API version you use.&lt;/p&gt;

&lt;p&gt;For a proof-of-concept or a hackathon project, the preview features are usable and the free tier of Azure AI Search is sufficient to get started. For a production deployment with SLA requirements, it's worth reviewing which specific capabilities you need against the GA boundary before committing to an architecture.&lt;/p&gt;

&lt;p&gt;The product page for Foundry IQ on Azure has a current overview of generally available capabilities: &lt;a href="https://azure.microsoft.com/products/ai-foundry/iq/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Foundry IQ on Azure&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a knowledge base programmatically
&lt;/h2&gt;

&lt;p&gt;The Python SDK for working with Foundry IQ knowledge bases uses &lt;code&gt;azure-search-documents&lt;/code&gt; version &lt;code&gt;2026-05-01-preview&lt;/code&gt; or later for the knowledge base surface, combined with &lt;code&gt;azure-ai-projects&lt;/code&gt; version &lt;code&gt;2.0.0&lt;/code&gt; or later for the agent integration. Both are available on PyPI with no additional package feeds.&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;from&lt;/span&gt; &lt;span class="n"&gt;azure.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultAzureCredential&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.search.documents.indexes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchIndexClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.search.documents.indexes.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;KnowledgeBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;KnowledgeSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SearchIndexKnowledgeSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;search_endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-search-service.search.windows.net&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;index_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchIndexClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-05-01-preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;knowledge_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KnowledgeSource&lt;/span&gt;&lt;span class="p"&gt;(&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;company-docs-source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchIndexKnowledgeSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;index_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;company-documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;knowledge_base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KnowledgeBase&lt;/span&gt;&lt;span class="p"&gt;(&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;company-docs-kb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Internal company documentation for agent grounding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;knowledge_sources&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;knowledge_source&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_or_update_knowledge_base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knowledge_base&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;Knowledge base created: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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;This creates a knowledge base that wraps an existing Azure AI Search index. The knowledge base inherits the permissions model of the underlying search service, so access control lists configured on the index are enforced at query time without additional configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting an agent to Foundry IQ
&lt;/h2&gt;

&lt;p&gt;Once the knowledge base exists, connecting a Foundry Agent to it requires declaring the knowledge base as an MCP tool in the agent configuration. The Foundry IQ MCP endpoint is exposed per knowledge base and any MCP-compatible agent can use it.&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;from&lt;/span&gt; &lt;span class="n"&gt;azure.ai.projects&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AIProjectClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.ai.projects.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;McpTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AgentMcpToolDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;project_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AIProjectClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-foundry-project.services.ai.azure.com/api/projects/your-project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;mcp_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;connection_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-search-service-connection-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tool_names&lt;/span&gt;&lt;span class="o"&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;knowledge_base_retrieve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;knowledge_base_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;company-docs-kb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_agent&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;gpt-4.1-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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;document-assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        You are an assistant that answers questions about company policy.
        When answering, always use the knowledge base to retrieve relevant information.
        You must never answer from your own training knowledge when company-specific
        information is available in the knowledge base.
        Always cite the source documents in your responses.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AgentMcpToolDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_tool&lt;/span&gt;&lt;span class="p"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent created: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;The &lt;code&gt;knowledge_base_retrieve&lt;/code&gt; tool is what allows the agent to query the knowledge base through agentic retrieval. When the agent receives a question, it uses this tool to plan queries, run parallel searches across the configured knowledge sources, and aggregate results with citations before generating a response.&lt;/p&gt;

&lt;h2&gt;
  
  
  How agentic retrieval works
&lt;/h2&gt;

&lt;p&gt;The retrieval process that happens when an agent queries a knowledge base is more than a keyword or vector search. The agentic retrieval engine analyzes the incoming query, decomposes it into sub-queries when necessary, searches the configured knowledge sources in parallel using a combination of keyword, vector, and hybrid search depending on the source type, enforces user permissions at query time by checking access control lists and Microsoft Purview sensitivity labels, and returns extractive content with citations that trace each answer fragment back to its source document.&lt;/p&gt;

&lt;p&gt;The reasoning effort applied to this process is configurable. Lower effort tiers use fewer tokens and respond faster at the cost of retrieval quality for complex multi-part questions. Higher effort tiers apply more query planning and source selection logic but consume more tokens per request. The appropriate tier depends on the agent's use case and the complexity of the questions it needs to answer.&lt;/p&gt;

&lt;p&gt;For production agents, the permission enforcement behavior is particularly relevant. Foundry IQ synchronizes access control lists for supported knowledge sources and enforces them at query time, which means the agent never returns content that the querying user doesn't have permission to see, even if that content exists in the knowledge base. This is handled at the knowledge layer rather than requiring the application to implement permission filtering separately.&lt;/p&gt;

&lt;p&gt;The full guide to connecting agents to Foundry IQ knowledge bases, including the current API version requirements and role assignments, is in Microsoft Learn: &lt;a href="https://learn.microsoft.com/azure/foundry/agents/how-to/foundry-iq-connect?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Connect Agents to Foundry IQ Knowledge Bases&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying the knowledge base directly
&lt;/h2&gt;

&lt;p&gt;Outside of a Foundry Agent context, knowledge bases can also be queried directly through the Azure AI Search REST API or SDK. This is useful for building custom application layers that need grounded retrieval without using Foundry Agent Service.&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;from&lt;/span&gt; &lt;span class="n"&gt;azure.search.documents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchClient&lt;/span&gt;

&lt;span class="n"&gt;search_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;index_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;company-documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-05-01-preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;search_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;knowledge_base_retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;knowledge_base_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;company-docs-kb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the expense reimbursement policy for travel?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;effort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&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;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&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;Source: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata_source&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;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;Content: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&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="si"&gt;}&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Score: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@search.score&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;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;---&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;The &lt;code&gt;effort&lt;/code&gt; parameter controls the reasoning tier applied to the query. Available values depend on which API preview version is being used, with &lt;code&gt;standard&lt;/code&gt; being the baseline tier available across versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use Foundry IQ versus building custom RAG
&lt;/h2&gt;

&lt;p&gt;Foundry IQ makes most sense when the same document corpus needs to serve multiple agents, when permission enforcement at the knowledge layer is a requirement, when the team doesn't want to own the chunking, embedding, and indexer infrastructure, or when the project is already built on Microsoft Foundry and Azure AI Search.&lt;/p&gt;

&lt;p&gt;A custom RAG implementation built directly on Azure AI Search makes more sense when the retrieval logic needs behavior that Foundry IQ doesn't expose as a configurable parameter, when the knowledge base abstraction adds more friction than it removes for a single-agent use case, or when specific preview features needed for the project might change in ways that would break a production deployment before reaching GA.&lt;/p&gt;

&lt;p&gt;The two approaches aren't mutually exclusive. The underlying Azure AI Search service is the same in both cases, which means migrating from a custom search index to a Foundry IQ knowledge base that wraps the same index is a configuration change rather than a data migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/foundry/agents/concepts/what-is-foundry-iq?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;What is Foundry IQ? - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/products/ai-foundry/iq/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Foundry IQ product page - Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/foundry/agents/how-to/foundry-iq-connect?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Connect Agents to Foundry IQ Knowledge Bases - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/search/agentic-retrieval-how-to-create-knowledge-base?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Create a knowledge base in Azure AI Search - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/training/modules/introduction-foundry-iq/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Build knowledge-enhanced AI agents with Foundry IQ - Microsoft Learn Training&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/foundry/build-smarter-agents-faster-with-foundry-iq/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Build smarter agents faster with Foundry IQ - Microsoft Foundry Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft documentation and verified sources as of September 2026. Foundry IQ features are actively evolving and some capabilities described may move from preview to general availability or change behavior between API versions. Always verify current status at Microsoft Learn before building production systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How to authenticate Azure apps without storing credentials</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Sat, 05 Sep 2026 18:22:22 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/why-azure-managed-identity-replaces-stored-credentials-and-how-to-use-it-in-2026-29cj</link>
      <guid>https://dev.to/carlosjcastrog/why-azure-managed-identity-replaces-stored-credentials-and-how-to-use-it-in-2026-29cj</guid>
      <description>&lt;p&gt;Every Azure project eventually has the same conversation. Where do we store the connection string? Someone suggests an environment variable, and someone else points out that environment variables end up in deployment pipelines, in Docker compose files, in Terraform state, and occasionally in accidental commits. A secret manager gets proposed, and the secret manager needs its own credentials to access the secrets. The problem recurses.&lt;/p&gt;

&lt;p&gt;Managed Identity doesn't solve the secret manager problem by adding another layer. It removes the credential from the equation entirely for workloads running inside Azure, so the application doesn't authenticate with a stored credential but as itself, using an identity that Azure manages automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Managed Identity actually does
&lt;/h2&gt;

&lt;p&gt;When you enable a Managed Identity on an Azure resource, Azure creates an identity in Microsoft Entra ID tied to that resource's lifecycle. The resource can then request short-lived tokens from the Azure Instance Metadata Service endpoint at &lt;code&gt;169.254.169.254&lt;/code&gt;, which is only reachable from within Azure infrastructure, and those tokens are what the resource uses to authenticate against other Azure services. There's nothing to store, nothing to rotate manually, and nothing that can be leaked in a repository because the credential never exists as a static string anywhere in your codebase or configuration.&lt;/p&gt;

&lt;p&gt;The key property from Microsoft's documentation is precise: managed identities give code running on an Azure resource access to other resources without developers needing to handle or put credentials directly into code. The emphasis on "code running on an Azure resource" matters because Managed Identity only works from within Azure. A local development machine can't reach the Instance Metadata Service endpoint, which means the local development flow still needs an alternative authentication mechanism, typically &lt;code&gt;az login&lt;/code&gt; or a service principal configured for development only.&lt;/p&gt;

&lt;h2&gt;
  
  
  System-assigned vs user-assigned
&lt;/h2&gt;

&lt;p&gt;There are two types of Managed Identity, and Microsoft's current recommendation, updated in its official best practice documentation, is that user-assigned identities are more efficient in a broader range of scenarios.&lt;/p&gt;

&lt;p&gt;A system-assigned identity is created directly on a resource and its lifecycle is tied to that resource, so when the resource is deleted, the identity is deleted too. This sounds convenient but creates a management problem at scale: every resource gets its own identity, every identity needs its own role assignments, and if you have twenty App Services that all need read access to the same storage account, you end up managing twenty separate identities with twenty separate role assignments that have to stay synchronized.&lt;/p&gt;

&lt;p&gt;A user-assigned identity is created as a standalone resource in Azure and can be assigned to multiple resources simultaneously, with its lifecycle independent of any particular resource. If you delete the App Service, the identity persists. You can pre-define what a user-assigned identity can access, get it approved by whoever owns your access control policy, and then assign it to new resources as they're created without going through a new approval cycle each time.&lt;/p&gt;

&lt;p&gt;Microsoft's naming recommendation makes the operational difference clear: name identities after their permission set rather than after the consumer. An identity named &lt;code&gt;id-blogreader-prod-eastus&lt;/code&gt; outlives any specific workload and its purpose stays readable in audit logs, while an identity named &lt;code&gt;id-appsvc-01&lt;/code&gt; doesn't communicate what it can do and its permissions tend to drift over time as the team changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The security mistake most developers make
&lt;/h2&gt;

&lt;p&gt;Turning on Managed Identity and removing the hardcoded credential is the right first step, but stopping there is where most teams leave a significant gap.&lt;/p&gt;

&lt;p&gt;Managed identities do not make a workload secure. They make it credential-free, and that distinction matters because the identity still holds whatever permissions you've granted it, and those permissions are available to anything running on the resource the identity is assigned to. Microsoft's own documentation states this explicitly: if a user has access to install or execute code on a resource with a managed identity, that user has access to everything the identity can reach, even if they have no direct access to those target resources.&lt;/p&gt;

&lt;p&gt;This means that granting a Managed Identity broad permissions like Contributor on a storage account and then assigning it to a shared compute resource where multiple teams run code effectively elevates every team's access to that storage account. The credential problem is gone but the permission problem remains and is now implicit rather than visible in a secrets manager.&lt;/p&gt;

&lt;p&gt;The practical fix is applying least-privilege role assignments: instead of Contributor, assign Storage Blob Data Reader if the workload only reads blobs, and instead of Key Vault Administrator, assign Key Vault Secrets User if the workload only reads secrets. Azure RBAC has purpose-specific built-in roles for most common scenarios, and using them reduces the blast radius if a workload is ever compromised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using DefaultAzureCredential
&lt;/h2&gt;

&lt;p&gt;The practical implementation for most teams is &lt;code&gt;DefaultAzureCredential&lt;/code&gt;, part of the Azure Identity SDK and available for Python, JavaScript, Java, and .NET. It tries a sequence of authentication methods in order and uses the first one that succeeds, which means the same code works in both local development and in Azure without any environment-specific branching.&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;from&lt;/span&gt; &lt;span class="n"&gt;azure.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultAzureCredential&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.storage.blob&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BlobServiceClient&lt;/span&gt;

&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BlobServiceClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;account_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;https://mystorageaccount.blob.core.windows.net&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a local environment, &lt;code&gt;DefaultAzureCredential&lt;/code&gt; typically picks up the credentials from &lt;code&gt;az login&lt;/code&gt;, and on an Azure resource with Managed Identity enabled it picks up the managed identity token from the Instance Metadata Service. The same line of code handles both cases.&lt;/p&gt;

&lt;p&gt;The order of credential resolution matters when debugging authentication issues. &lt;code&gt;DefaultAzureCredential&lt;/code&gt; tries EnvironmentCredential, WorkloadIdentityCredential, ManagedIdentityCredential, SharedTokenCacheCredential, VisualStudioCodeCredential, AzureCliCredential, AzurePowerShellCredential, and AzureDeveloperCliCredential in that sequence. If you're seeing unexpected authentication behavior in local development, it's usually because an earlier credential in the chain is being picked up unexpectedly, typically an environment variable in the shell that's overriding the &lt;code&gt;az login&lt;/code&gt; credential.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DefaultAzureCredential&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@azure/identity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SecretClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@azure/keyvault-secrets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SecretClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://mykeyvault.vault.azure.net&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;credential&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-database-connection-string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where Managed Identity doesn't work
&lt;/h2&gt;

&lt;p&gt;Two scenarios where a service principal with a client secret or certificate is still the right choice: workloads running outside Azure that can't reach the Instance Metadata Service, and federated scenarios where the workload needs to authenticate to Azure from a non-Azure environment like GitHub Actions or another cloud provider. For GitHub Actions specifically, Microsoft supports OIDC-based federation that eliminates the stored secret problem without requiring the workload to run in Azure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2026 shift toward identity-first
&lt;/h2&gt;

&lt;p&gt;Azure's direction in 2026 is increasingly identity-first by default, with system-assigned managed identities now the default for Kubernetes workloads on AKS, replacing API keys in that context. The pattern is becoming a baseline expectation rather than an advanced configuration, and for new projects starting on Azure, configuring Managed Identity from the beginning is significantly easier than retrofitting it into an existing codebase that passes credentials around as strings.&lt;/p&gt;

&lt;p&gt;For the full Microsoft documentation on Managed Identity and how to configure it for specific Azure services:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Managed identities for Azure resources - Overview&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/managed-identity-best-practice-recommendations?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Best practice recommendations for managed identities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview-for-developers?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Managed identities for Azure resources - Developer introduction and guidelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/python/sdk/authentication/credential-chains?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Use DefaultAzureCredential in an application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/app-service/overview-managed-identity?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Managed identity for Azure App Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/security/fundamentals/identity-management-best-practices?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure identity management and access control best practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft documentation and verified sources as of September 2026. Azure services and recommendations may change. Verify current guidance at Microsoft Learn before implementing in production environments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>security</category>
      <category>cloud</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to activate Azure for Students without a university email in 2026</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:07:32 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/how-to-activate-azure-for-students-without-a-university-email-in-2026-5d98</link>
      <guid>https://dev.to/carlosjcastrog/how-to-activate-azure-for-students-without-a-university-email-in-2026-5d98</guid>
      <description>&lt;p&gt;Azure for Students gives enrolled university students $100 in Azure credits, access to free services, and no credit card requirement. The standard path to activate it assumes your university email domain is registered in Microsoft's verification system, and for many institutions, especially in regions outside North America and Europe, it simply isn't. When you enter your university email and get the message "your email domain is not currently registered with us", that's not a rejection. It's the system telling you to use a different verification method.&lt;/p&gt;

&lt;p&gt;There are two paths that work when your institutional domain isn't recognized: verifying with official academic documentation, or using the GitHub Student Developer Pack as proof of student status. Both are supported by Microsoft and SheerID, the verification provider behind Azure for Students.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need before starting either path
&lt;/h2&gt;

&lt;p&gt;Regardless of which path you take, you need a Microsoft account. If you already have an Outlook or Hotmail account, that works. If you don't, create one at account.microsoft.com before anything else. This is the account that will hold your Azure for Students subscription and the same one you will use later if you apply to the Microsoft Student Ambassadors program, so use one you plan to keep active.&lt;/p&gt;

&lt;p&gt;Your Microsoft account does not need to be linked to your university email. A personal Gmail cannot be a Microsoft account on its own, but you can create a Microsoft account using a Gmail address at signup.live.com. Either way, what matters is that you have a stable Microsoft account ready before starting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 1: Documentation-based verification
&lt;/h2&gt;

&lt;p&gt;This path works when you don't have access to a recognized institutional email and you're not yet verified on GitHub Education. It requires uploading an official document that proves your current enrollment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Go to the Azure for Students page&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Navigate to &lt;code&gt;azure.microsoft.com/free/students&lt;/code&gt; and click the "Activate now" button. Sign in with your Microsoft account when prompted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Enter your information&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The form will ask for your email address. Enter your Microsoft account email, not your university email. When the system cannot verify the domain automatically, it will present an alternative verification option where you can submit academic documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Upload your enrollment document&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The document that works is an official enrollment certificate or student registration certificate issued by your university. It needs to show your full name, your institution's name, and confirmation of your current enrollment status. A student ID card alone is generally not sufficient because it doesn't always show the enrollment year or current academic status. An official letter or certificate from the registrar's office is the correct document.&lt;/p&gt;

&lt;p&gt;In my case I uploaded a constancia de alumno regular, which is the standard enrollment certificate issued by Argentine universities, and the verification was accepted without issues.&lt;/p&gt;

&lt;p&gt;The verification is processed by SheerID. In most cases it completes within minutes, but it can take up to 48 hours. If it hasn't resolved after 48 hours, you can contact Azure Education Support at &lt;code&gt;azureforeducation.microsoft.com/institutions/contact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Confirm activation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once verified, you'll receive a confirmation email and your Azure for Students subscription will appear in the Azure portal at &lt;code&gt;portal.azure.com&lt;/code&gt;. The subscription includes $100 in credits valid for 12 months and access to a set of always-free services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 2: GitHub Student Developer Pack
&lt;/h2&gt;

&lt;p&gt;This path works if you already have or plan to get the GitHub Student Developer Pack. GitHub Education accepts enrollment documents and student ID cards for verification, and once approved, the Pack can be used to activate Azure for Students directly without repeating the documentation process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Apply for the GitHub Student Developer Pack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;education.github.com/pack&lt;/code&gt; and click "Get student benefits". Sign in with your GitHub account and follow the verification flow. You'll need to provide your university name, your enrollment email if you have one, and upload a proof of enrollment document if your email isn't recognized. A student ID, enrollment certificate, or official letter from your university all work here. GitHub's verification usually takes up to 72 hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Wait for confirmation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub sends a confirmation email when your student status is verified. The Pack and all its benefits become active at that point. Don't try to activate Azure before receiving that confirmation because the verification link won't work until GitHub completes the process on their end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Activate Azure for Students through the Pack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once verified, go to &lt;code&gt;signup.azure.com/studentverification&lt;/code&gt; and sign in with your Microsoft account. When the page asks how you want to verify your student status, select the option to connect with GitHub and complete the GitHub OAuth step. This confirms to Microsoft that you're a verified student through GitHub Education.&lt;/p&gt;

&lt;p&gt;After completing the OAuth step, wait a few minutes and then open the Azure portal at &lt;code&gt;portal.azure.com&lt;/code&gt;. Your Azure for Students subscription should appear active in the subscriptions list. If it doesn't appear after 10 minutes, try signing out and back in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you get an error saying your GitHub account doesn't have student benefits enabled&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This error can appear even when your Student Developer Pack is active and verified. The most reliable fix is to open a support ticket with GitHub Education by going to &lt;code&gt;support.github.com&lt;/code&gt; and selecting "I am a verified student having trouble redeeming my Global Campus offers." GitHub support typically resolves these within a few hours.&lt;/p&gt;

&lt;p&gt;Do not try to activate Azure multiple times before resolving this error. Azure for Students is limited to one activation per Microsoft account. If you trigger multiple failed attempts, the offer may become unavailable for that account and you'll need to contact Azure Education Support to reset it.&lt;/p&gt;

&lt;h2&gt;
  
  
  If both paths fail: manual support ticket
&lt;/h2&gt;

&lt;p&gt;Neither path above is guaranteed to work in every situation. The official Azure for Students documentation states that verification requires an institutional email address, and the document-upload option in the self-service flow is not available in all regions or for all institutions. If you complete both paths and still cannot activate the subscription, a manual support ticket is the next step.&lt;/p&gt;

&lt;p&gt;This process is not officially documented as a standard path, but multiple Microsoft Q&amp;amp;A threads from 2026 confirm that Microsoft's education support team can manually review student eligibility when the automated system fails. The outcome is not guaranteed, but it is the correct escalation channel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to prepare before opening a ticket:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You will need clear scans or photos of your student ID card showing your name and current academic year, an official enrollment certificate or letter from your university's registrar office showing your full name, institution name, and current enrollment period, and a screenshot of the error or rejection message you received during the self-service verification flow. If you completed the GitHub Student Developer Pack, include a screenshot confirming your approved status there as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to open the ticket:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you already have any Azure account (including a free account), go to &lt;code&gt;portal.azure.com&lt;/code&gt;, click "Help + Support" in the top right corner, and create a new support request. Select "Subscription Management" as the issue type, then "Account" and "Azure for Students verification" as the subcategories. Attach your documents and explain that your institution does not provide an institutional email address and that you are requesting manual verification.&lt;/p&gt;

&lt;p&gt;If you do not have any Azure account yet and cannot access the portal, use the Azure for Education contact form directly:&lt;/p&gt;

&lt;p&gt;👉 &lt;code&gt;azureforeducation.microsoft.com/institutions/contact&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can also post your situation with full details in the official Microsoft Q&amp;amp;A forum at &lt;code&gt;learn.microsoft.com/answers&lt;/code&gt;, where Microsoft moderators actively respond to student verification questions and can escalate your case internally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative if manual verification is not granted:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If Microsoft confirms that your institution is not supported and manual verification cannot be completed, the Azure for Students Starter program at &lt;code&gt;azure.microsoft.com/free/students/starter&lt;/code&gt; provides access to a limited set of free Azure services without requiring student verification or a credit card. It does not include the $100 credit, but it gives access to services like App Service, Azure Functions, and Azure SQL Database at no cost, which may be sufficient for learning purposes.&lt;/p&gt;




&lt;p&gt;Once your Azure for Students subscription is active, the Microsoft account holding it is the one you use for everything related to Microsoft programs. If you plan to apply to the Microsoft Student Ambassadors program, you'll register at &lt;code&gt;studentambassadors.microsoft.com&lt;/code&gt; using this same Microsoft account. The program verifies your Azure for Students subscription during registration, and the button to proceed only becomes active once that subscription is confirmed.&lt;/p&gt;

&lt;p&gt;To check your credit balance, remaining time, and the services included in your subscription, go to the Azure portal at &lt;code&gt;portal.azure.com&lt;/code&gt; and look for the &lt;strong&gt;Education&lt;/strong&gt; section in the left navigation menu. That is where Azure for Students accounts track their credits and usage, separately from the general Subscriptions section that paid accounts use. The $100 in credits is valid for 12 months from activation, and unused credits do not roll over, so it's important starting to explore Azure services once the subscription is active.&lt;/p&gt;

&lt;p&gt;For a full guide on the Microsoft Student Ambassadors program and how to get started after activating Azure for Students:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://learn.microsoft.com/azure/education-hub/azure-dev-tools-teaching/azure-students-program?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;https://learn.microsoft.com/azure/education-hub/azure-dev-tools-teaching/azure-students-program?wt.mc_id=studentamb_510930&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The information in this article is based on official Microsoft documentation, GitHub Education documentation, and personal experience as of September 2026. Verification processes, eligibility requirements, and available benefits may change at any time without notice. This article is provided for informational purposes only. The author is not responsible for any issues arising from following these steps, including but not limited to failed verifications, credit losses, or account restrictions. Always verify current requirements directly on the official Azure for Students page and the GitHub Student Developer Pack page before proceeding.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>microsoft</category>
      <category>cloud</category>
      <category>learning</category>
    </item>
    <item>
      <title>Azure OpenAI Service vs OpenAI API, which to use and when in 2026</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Wed, 26 Aug 2026 00:12:06 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/azure-openai-service-vs-openai-api-which-to-use-and-when-in-2026-1nic</link>
      <guid>https://dev.to/carlosjcastrog/azure-openai-service-vs-openai-api-which-to-use-and-when-in-2026-1nic</guid>
      <description>&lt;p&gt;When someone asks whether to use Azure OpenAI Service or the direct OpenAI API, the starting point is this: the models running on both platforms are identical. GPT-4o, GPT-5, and the o-series models you deploy on Azure have the same weights, the same capabilities, and the same output quality as the ones you call from platform.openai.com, and what changes between the two platforms is the infrastructure where they run, the authentication mechanism, and the compliance guarantees the provider can offer on those requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in 2026
&lt;/h2&gt;

&lt;p&gt;Azure AI Foundry was renamed Microsoft Foundry on January 1, 2026, and Azure OpenAI Service now lives inside that unified platform alongside the model catalog, development tooling, and agents. References to Microsoft Foundry in new documentation point to what used to be Azure AI Foundry.&lt;/p&gt;

&lt;p&gt;In July 2026, the GPT-5.6 family arrived with Sol, Terra, and Luna available on Azure the same day as on the direct OpenAI API. Historically Azure lagged four to eight weeks behind new model releases because Microsoft validates them within their compliance frameworks before making them available, and while that gap still exists for some specific features and APIs, for the main models in the GPT-5 family availability is converging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where data is processed
&lt;/h2&gt;

&lt;p&gt;When you call GPT-4o from the OpenAI API, the request goes to OpenAI's own infrastructure, which is centralized and gives you no control over which region processes your data. For most use cases that doesn't matter, but for organizations with data residency requirements, regulatory compliance needs, or industries like healthcare, banking, or government, that detail can determine whether the service is usable at all.&lt;/p&gt;

&lt;p&gt;Azure OpenAI runs the same models within the boundary of your Azure tenant, so the data you send in prompts doesn't leave to OpenAI's infrastructure but processes in the Azure regions you choose. That's what makes it possible to meet HIPAA, SOC 2, EU data residency, and other certifications that companies in regulated industries need before they can deploy to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;The OpenAI API uses API keys, strings you need to store, rotate, distribute, and protect from ending up in a repository. Azure OpenAI can authenticate using &lt;code&gt;DefaultAzureCredential&lt;/code&gt;, which delegates authentication to Microsoft Entra ID and can use Managed Identity so the service obtains tokens automatically without any hardcoded or stored credentials anywhere.&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;from&lt;/span&gt; &lt;span class="n"&gt;azure.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultAzureCredential&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AzureOpenAI&lt;/span&gt;

&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cognitiveservices.azure.com/.default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AzureOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;azure_endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://my-resource.openai.azure.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;azure_ad_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-04-01-preview&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;In environments where a security review is required before deploying anything to production, being able to remove API keys from the authentication flow carries practical weight in the approval process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing and billing modes
&lt;/h2&gt;

&lt;p&gt;Prices for the main models are comparable between the two platforms. The GPT-5.6 family in Global Standard on Azure follows the same OpenAI list rates, with ranges from $0.20 to $5 per million input tokens for mainstream models in the current catalog. For the GPT-5.6 Sol model, Azure announced promotional pricing of $4.00 per million input tokens and $20.00 per million output tokens from September 1 through at least November 30, 2026.&lt;/p&gt;

&lt;p&gt;Azure has Provisioned Throughput Units (PTUs), reserved capacity blocks you pay for hourly rather than per token. When your application's sustained usage exceeds 60-70% of a PTU's capacity, that mode starts making economic sense. Below that threshold, pay-as-you-go per token is more efficient.&lt;/p&gt;

&lt;p&gt;Something most pricing guides don't mention is that in enterprise deployments with private networking, Azure adds supporting infrastructure costs including Azure AI Search, Blob Storage, private endpoints, and network egress that can add 15 to 40% on top of token costs in full private networking production deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content filtering
&lt;/h2&gt;

&lt;p&gt;OpenAI has baseline moderation that runs on all requests. Azure OpenAI lets you configure custom filters through Azure AI Content Safety, with granular control over the thresholds for each content category based on your application's specific needs. For medical use cases where certain clinical terms might trigger general filters, that granularity can be necessary for the service to work correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use each one
&lt;/h2&gt;

&lt;p&gt;The direct OpenAI API makes sense when you're prototyping and need setup speed without bureaucracy, when you want immediate access to new features without waiting for Azure's validation cycle, or when you're building a consumer application where compliance requirements aren't a blocker. It has no quota gate on the standard tier, so you can start calling the API the same day without requesting additional capacity.&lt;/p&gt;

&lt;p&gt;Azure OpenAI makes sense when the project needs to meet specific regulations for the industry, when you need data to process within a specific Azure region, when the organization already has contracts and governance on Azure and needs the AI service integrated into that ecosystem, or when you need credential-free authentication through Managed Identity. Quota requests for high-demand models in specific regions can take a week or more to process, so starting that process early helps.&lt;/p&gt;

&lt;p&gt;A common and perfectly valid scenario is using both: you prototype with the OpenAI API which gives immediate access to everything, and when the project moves to production with compliance or private networking requirements, you migrate to Azure OpenAI with minimal code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating from one to the other
&lt;/h2&gt;

&lt;p&gt;If you already have code pointing to the OpenAI API and want to move it to Azure, the change is configuration rather than logic. The client changes from &lt;code&gt;OpenAI&lt;/code&gt; to &lt;code&gt;AzureOpenAI&lt;/code&gt;, you add your Azure resource endpoint and &lt;code&gt;api_version&lt;/code&gt;, and the rest of the code stays the same. Model names change because in Azure you deploy named instances rather than calling the model by its global 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="c1"&gt;# Before (direct OpenAI)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk-...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After (Azure OpenAI)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AzureOpenAI&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AzureOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;azure_endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://my-resource.openai.azure.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&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;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-04-01-preview&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;With Managed Identity, the &lt;code&gt;api_key&lt;/code&gt; disappears from the code entirely and is replaced by the Entra ID credential flow shown above.&lt;/p&gt;

&lt;p&gt;To explore Azure OpenAI Service and the model catalog available in Microsoft Foundry:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://azure.microsoft.com/products/ai-services/openai-service/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;https://azure.microsoft.com/products/ai-services/openai-service/?wt.mc_id=studentamb_510930&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft documentation and verified sources as of August 25, 2026. Prices and model availability may change. Verify current figures on the official Azure OpenAI pricing page before making architecture or budget decisions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>ai</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Bug That Kept Coming Back in Framer Motion</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Sat, 22 Aug 2026 16:46:56 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/the-bug-that-kept-coming-back-in-framer-motion-2kmh</link>
      <guid>https://dev.to/carlosjcastrog/the-bug-that-kept-coming-back-in-framer-motion-2kmh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Entiscore is an agent that audits a website's digital entity, checking schema markup, identity consistency, authority signals, and technical accessibility before returning a scored report. Somewhere along the way I decided the report needed some animation to it, scroll-triggered entrance animations built with Framer Motion's &lt;code&gt;whileInView&lt;/code&gt;, a blur-to-focus reveal for cards, and staggered timing for lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptoms
&lt;/h2&gt;

&lt;p&gt;The first time it happened, the Hero title on the landing page got stuck mid-animation, permanently blurred, permanently offset, frozen in its &lt;code&gt;hidden&lt;/code&gt; state with no way to recover. A hard refresh fixed it but scrolling did nothing, and it just sat there broken until the page reloaded.&lt;/p&gt;

&lt;p&gt;I fixed it, or so I thought.&lt;/p&gt;

&lt;p&gt;A few days later a different section broke the exact same way, with four feature cards on the homepage stuck in a half-rendered blur that never resolved to their final state. Same symptom, different component. I fixed that one too and moved on.&lt;/p&gt;

&lt;p&gt;Then it happened a third time, in the actual product report, in the "Evaluation by axis" section, which is the part of the UI a judge would actually look at during a demo. Three different components, three apparent bugs, the exact same failure mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  The investigation
&lt;/h2&gt;

&lt;p&gt;Each individual fix had worked in isolation, which was the trap. I kept treating the symptom as local, this specific card's animation is broken so I'd rewrite its transition and move on, and that approach papers over the actual defect instead of finding it.&lt;/p&gt;

&lt;p&gt;The question I should have asked the first time was what these three components had in common. The answer was a shared helper that looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getVariants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;motionSafe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Variants&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Variants&lt;/span&gt; &lt;span class="p"&gt;{&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="nx"&gt;motionSafe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&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="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Called inline, inside the component body, on every single render:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;motion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
  &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;getVariants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;motionSafe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blurReveal&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="nx"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;whileInView&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Root cause
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;getVariants&lt;/code&gt; returns a brand-new object reference every time it runs, even when the underlying values are identical. Framer Motion tracks the &lt;code&gt;variants&lt;/code&gt; prop by reference rather than by deep equality, so on every re-render it received what looked like a completely new set of animation variants.&lt;/p&gt;

&lt;p&gt;Combined with &lt;code&gt;viewport={{ once: true }}&lt;/code&gt;, that created a serious problem. The transition to &lt;code&gt;"visible"&lt;/code&gt; needs to be triggered by the intersection observer callback, but if a re-render swaps out the variants object mid-transition or right as the observer fires, the animation state and the variants object fall out of sync. The component ends up holding a &lt;code&gt;visible&lt;/code&gt; state that points to a variants object that no longer matches what's actually being interpolated, and because &lt;code&gt;once: true&lt;/code&gt; means the trigger only fires a single time, there's no second chance to self-correct.&lt;/p&gt;

&lt;p&gt;The Hero title, the feature cards, and the axis evaluation cards weren't three separate bugs but the same defect hit three separate times, because the anti-pattern lived in one function and got reused everywhere Framer Motion needed a reduced-motion fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Once I stopped treating the problem as three isolated incidents and searched the entire codebase for every call site of &lt;code&gt;getVariants&lt;/code&gt; and its sibling &lt;code&gt;getStaggerVariants&lt;/code&gt;, the fix itself was straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CARD_VARIANTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blur(4px)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blur(0px)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REDUCED_MOTION_VARIANTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Variants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;motion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
  &lt;span class="nx"&gt;variants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;motionSafe&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;CARD_VARIANTS&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REDUCED_MOTION_VARIANTS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;whileInView&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Constants defined once, outside the render path, with a simple ternary instead of a function call. The object reference is now stable across renders so Framer Motion's internal tracking never gets confused about which variants it's interpolating toward. Six files had the same pattern and all six got fixed in a single pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;p&gt;Before the fix, cards and titles occasionally rendered permanently blurred and never recovered without a full page reload, reproducible but not consistently, which made it easy to treat each occurrence as unrelated to the others.&lt;/p&gt;

&lt;p&gt;After the fix, every scroll-triggered reveal in the app resolves correctly, every time, across every entry point I could find.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Animation libraries that key transitions off object identity are unforgiving of helper functions that build config objects inline. If you're writing a function that returns a &lt;code&gt;Variants&lt;/code&gt; object, a &lt;code&gt;style&lt;/code&gt; object, or anything else React or a library will diff by reference, and you're calling that function during render, the reference changes on every render even when the values don't. Hoisting it to a constant outside the component lets the render function pick between two stable references instead of generating a new object every time.&lt;/p&gt;

&lt;p&gt;The other thing that came out of this was more about process than code. The third occurrence was the only one I actually investigated properly, and it was the only one that produced a fix that stuck, because it was the first time I looked for what the three broken components had in common instead of patching the one in front of me.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built as part of &lt;a href="https://entiscore.vercel.app" rel="noopener noreferrer"&gt;Entiscore&lt;/a&gt;, an entity-audit agent built for the Kiro powered by AWS hackathon by Código Facilito.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>kiro</category>
      <category>typescript</category>
    </item>
    <item>
      <title>overflow: clip saved my navbar</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Fri, 21 Aug 2026 23:34:30 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/overflow-clip-saved-my-navbar-2a99</link>
      <guid>https://dev.to/carlosjcastrog/overflow-clip-saved-my-navbar-2a99</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There was no error in the console, the CSS was syntactically correct, the logic was sound, and &lt;code&gt;overflow: hidden&lt;/code&gt; was just not working.&lt;/p&gt;

&lt;p&gt;I was building the navigation overlay for my personal portfolio, which runs on Next.js 15 with App Router, React 19 and Tailwind v4. The navbar opens a fullscreen overlay that descends from the top using GSAP. Inside that overlay, each navigation link has a split reveal effect: the link text sits in white with a dark layer underneath it, and on hover the dark layer rises to cover the white text while dark-colored text climbs up from below to replace it. The classic typographic flip that you see in agency sites and high-end portfolios.&lt;/p&gt;

&lt;p&gt;The overlay itself had &lt;code&gt;position: fixed&lt;/code&gt; to cover the entire viewport. That part worked fine.&lt;/p&gt;

&lt;p&gt;The text effect required wrapping each link in a container with &lt;code&gt;overflow: hidden&lt;/code&gt; so that the rising dark layer and the incoming dark text would be clipped until they entered the visible area. Without that clip, both layers show simultaneously and the menu looks like every link is doubled in two colors, which is exactly what I was seeing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six things that didn't fix it
&lt;/h2&gt;

&lt;p&gt;The first attempt was &lt;code&gt;overflow: hidden&lt;/code&gt; directly on the link wrapper container, but nothing changed and both text layers stayed visible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.nav-link-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* did nothing */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I switched to a fixed pixel height on the container calculated manually to match the line height. The thinking was that maybe the browser needed an explicit dimension to clip against and... still nothing.&lt;/p&gt;

&lt;p&gt;Then I tried &lt;code&gt;clip-path: inset(0 0 100% 0)&lt;/code&gt; animated in CSS, which clips the element's painted area directly without involving overflow behavior at all. The layers kept showing through.&lt;/p&gt;

&lt;p&gt;I moved &lt;code&gt;overflow: hidden&lt;/code&gt; up to the &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; element with no difference, then to the &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;, which made things worse because now it was affecting the whole list.&lt;/p&gt;

&lt;p&gt;At that point I started suspecting a specificity conflict or a CSS-in-JS issue, so I switched to inline styles in React to eliminate any possible cascade interference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.2em&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  ...
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same result, both layers still visible.&lt;/p&gt;

&lt;p&gt;After six attempts there was no progress, no error messages and nothing in the console pointing anywhere useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was actually happening
&lt;/h2&gt;

&lt;p&gt;After enough time searching I found the answer in the CSS specification. &lt;code&gt;overflow: hidden&lt;/code&gt; does not just clip content, it also creates what the spec calls a Block Formatting Context, which establishes an independent layout environment for the element and its descendants.&lt;/p&gt;

&lt;p&gt;There's a critical exception to this though: a Block Formatting Context cannot contain elements that are descendants of an ancestor with &lt;code&gt;position: fixed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The overlay had &lt;code&gt;position: fixed&lt;/code&gt;. Every element inside it, including my link wrappers trying to use &lt;code&gt;overflow: hidden&lt;/code&gt;, existed as a descendant of a fixed-positioned ancestor. When a child element tries to establish a BFC through &lt;code&gt;overflow: hidden&lt;/code&gt; but its nearest ancestor with a stacking context is &lt;code&gt;position: fixed&lt;/code&gt;, the browser cannot create the expected formatting context correctly. The clipping behavior does not apply.&lt;/p&gt;

&lt;p&gt;This interaction is defined in the CSS specification. It's not a bug in any browser. Chrome, Firefox and Safari all behave the same way because they're all following the spec. The problem is that &lt;code&gt;overflow: hidden&lt;/code&gt; works in probably 99% of cases people use it, so nobody builds a mental model of when it doesn't.&lt;/p&gt;

&lt;p&gt;There's also nothing diagnostic about the failure. The element renders. The CSS applies. The browser just quietly doesn't clip the overflow because the BFC can't be established in that context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;The answer was &lt;code&gt;overflow: clip&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.nav-link-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* instead of overflow: hidden */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;overflow: clip&lt;/code&gt; was introduced in CSS as a more precise clipping mechanism and unlike &lt;code&gt;overflow: hidden&lt;/code&gt; it does not create a Block Formatting Context. It clips the painted content visually without establishing any formatting context at all, which means there's no conflict with the &lt;code&gt;position: fixed&lt;/code&gt; ancestor and the clipping works exactly as expected, cutting off content that goes outside the element's bounds without any stacking context complications.&lt;/p&gt;

&lt;p&gt;For the user, &lt;code&gt;overflow: clip&lt;/code&gt; and &lt;code&gt;overflow: hidden&lt;/code&gt; look identical when they both work. The difference is only in what the browser does internally, and that internal difference is what determines whether clipping actually happens inside a fixed overlay.&lt;/p&gt;

&lt;p&gt;The text effect works now and the dark layer rises on hover, the white text disappears beneath it, the dark text climbs into view from below, and both layers clip cleanly at the container boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this one is worth knowing
&lt;/h2&gt;

&lt;p&gt;Most CSS bugs announce themselves. A layout breaks visually in an obvious way, or the console flags something, or the behavior changes between browsers in a way that points to a compatibility issue. This one didn't. The code looked right because it was right, just using a property that had a documented edge case that almost no practical documentation mentions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;overflow: clip&lt;/code&gt; has been supported in all major browsers since 2022, so it's not new, but because &lt;code&gt;overflow: hidden&lt;/code&gt; handles the vast majority of clipping scenarios without issue, most developers have never needed to reach for it. If you're building UI where overflow clipping needs to work inside a &lt;code&gt;position: fixed&lt;/code&gt; container, whether that's a fullscreen overlay, a fixed sidebar, or a sticky header with animated content inside it, &lt;code&gt;overflow: clip&lt;/code&gt; is the right tool.&lt;/p&gt;

&lt;p&gt;The six failed attempts weren't wasted. Working through &lt;code&gt;clip-path&lt;/code&gt;, inline styles and every possible container element confirmed that the problem wasn't specificity, wasn't React, wasn't Tailwind, and wasn't any configuration issue. It was the CSS spec doing exactly what the CSS spec says, in a case where the spec and the intuition don't match.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Portfolio built with Next.js 16, React 19 and Tailwind v4. Available at &lt;a href="https://carlosjcastrog.com" rel="noopener noreferrer"&gt;carlosjcastrog.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>nextjs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The lighthouse that leaked WebGL memory every time you changed the theme</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Thu, 20 Aug 2026 11:48:36 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/the-lighthouse-that-leaked-webgl-memory-every-time-you-changed-the-theme-4mib</link>
      <guid>https://dev.to/carlosjcastrog/the-lighthouse-that-leaked-webgl-memory-every-time-you-changed-the-theme-4mib</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;FaroIQ is a strategic intelligence platform for nonprofits that I built for the Microsoft Agents League Hackathon 2026. It runs a 9-agent pipeline on Azure AI Foundry, produces a full strategic report in under 90 seconds, and executes automatically into Microsoft 365.&lt;/p&gt;

&lt;p&gt;The hero section was one of the most important design decisions of the project. In a hackathon, the first screen a judge sees defines the impression of everything that follows. I wanted something that represented the name FaroIQ, which means lighthouse in Spanish, without falling into the generic hero with a gradient, a headline, and a CTA button that every project ends up with.&lt;/p&gt;

&lt;p&gt;So I built a fully custom 3D lighthouse with Three.js from scratch: cylindrical tower segments with alternating stone and stripe materials, a lantern room with a rotating beam using a &lt;code&gt;SpotLight&lt;/code&gt; and additive blending cones, animated wave geometry updated every frame, star particles in dark mode, a sun sphere in light mode, directional and point lights, fog with different density between modes, and a lens sphere with pulsing opacity. The lighthouse was not decorative. It was functional to the concept and it was what made the hero feel like something built with intention rather than assembled from a template.&lt;/p&gt;

&lt;p&gt;The app also had a dark and light mode toggle. The lighthouse had to look completely different in each mode. Different fog density, different beam intensity, different water color, stars appearing and disappearing, moon versus sun. That meant the component received an &lt;code&gt;isDark&lt;/code&gt; prop and rebuilt its entire material palette and scene configuration based on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;React 18 + Vite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D library&lt;/td&gt;
&lt;td&gt;Three.js r168&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Affected environments&lt;/td&gt;
&lt;td&gt;Local development and production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;Chrome, Firefox, Safari (all affected)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What was happening
&lt;/h2&gt;

&lt;p&gt;The bug had multiple faces and none of them were consistent, which made it harder to pin down.&lt;/p&gt;

&lt;p&gt;On the first load, the lighthouse sometimes did not finish rendering at all. The browser would stall partway through the Three.js initialization and the canvas would stay black. In production, a judge opening the app for the first time might see nothing in the hero section.&lt;/p&gt;

&lt;p&gt;When the user clicked the theme toggle, one of several things happened depending on timing and how much the browser had already used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The lighthouse froze mid-transition with both the old and new scene partially rendered at the same time&lt;/li&gt;
&lt;li&gt;The scene switched correctly but the browser's memory footprint climbed with each toggle&lt;/li&gt;
&lt;li&gt;Nothing happened at all and the lighthouse stayed stuck in the previous mode regardless of the new &lt;code&gt;isDark&lt;/code&gt; value&lt;/li&gt;
&lt;li&gt;The browser tab became unresponsive and had to be killed
The performance tab in Chrome DevTools showed the GPU memory climbing with each theme switch instead of staying flat. The console showed:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARNING: Too many active WebGL contexts. Oldest context will be lost.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And intermittently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebGL: INVALID_OPERATION: drawArrays: no buffer is bound to enabled attribute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bug was reproducible every time. Sometimes it took one toggle to surface it, sometimes two or three but it always happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites to understand the bug
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How React's useEffect cleanup works with dependencies
&lt;/h3&gt;

&lt;p&gt;When a &lt;code&gt;useEffect&lt;/code&gt; has dependencies, React runs the cleanup function and then re-runs the effect whenever any dependency changes. This happens within the same component instance. The component does not unmount. The refs stay the same. The DOM node stays the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  How WebGL contexts work in the browser
&lt;/h3&gt;

&lt;p&gt;Every &lt;code&gt;new THREE.WebGLRenderer()&lt;/code&gt; call creates a new WebGL context bound to a new canvas element. Browsers impose a hard limit on how many active WebGL contexts a page can have. Chrome's limit is around 16. When that limit is exceeded, the browser starts discarding the oldest contexts to make room for new ones.&lt;/p&gt;

&lt;p&gt;The critical detail is that &lt;code&gt;renderer.dispose()&lt;/code&gt; releases the Three.js resources on the JavaScript side, but the GPU does not necessarily free the underlying context memory synchronously. The browser's GPU process has its own lifecycle that does not block the JavaScript thread. Calling &lt;code&gt;dispose()&lt;/code&gt; and then immediately calling &lt;code&gt;new THREE.WebGLRenderer()&lt;/code&gt; in the same component lifetime can create a new context before the old one is fully released at the GPU level.&lt;/p&gt;

&lt;p&gt;This is the gap the bug lived in.&lt;/p&gt;




&lt;h2&gt;
  
  
  The component structure
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;LighthouseBackground&lt;/code&gt; was always its own isolated component, which was the right decision. All the Three.js setup, animation loop, and cleanup lived inside a single &lt;code&gt;useEffect&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LighthouseBackground&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mountRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLDivElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mountRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&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="nx"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WebGLRenderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;antialias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;W&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPixelRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devicePixelRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// ... full scene setup: materials, geometries, lights, animation loop&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onResize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nx"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;mountRef&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;absolute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cleanup looked correct. Cancel the animation frame, remove the canvas, dispose the renderer. All the right things in the right order.&lt;/p&gt;

&lt;p&gt;The problem was that with &lt;code&gt;[isDark]&lt;/code&gt; as the dependency, React ran this cleanup and immediately re-ran the effect in the same component instance when the theme changed. The old WebGL context was not guaranteed to be fully released by the GPU before the new &lt;code&gt;WebGLRenderer&lt;/code&gt; constructor call created another one. With a scene this heavy, that gap was enough to trigger the memory accumulation and the browser warnings.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ThemeToggle&lt;/code&gt; component was wiring directly into the theme context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ThemeToggle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggle&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{...}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FiSun&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt; : &amp;lt;FiMoon size=&lt;/span&gt;&lt;span class="se"&gt;{15}&lt;/span&gt;&lt;span class="sr"&gt; /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each click updated &lt;code&gt;theme&lt;/code&gt; in the context, which re-rendered &lt;code&gt;HeroSection&lt;/code&gt;, which passed the new &lt;code&gt;isDark&lt;/code&gt; to &lt;code&gt;LighthouseBackground&lt;/code&gt;, which triggered the effect cycle described above.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reproduction steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository and run &lt;code&gt;npm install &amp;amp;&amp;amp; npm run dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open the app in Chrome with DevTools open on the Performance and Console tabs&lt;/li&gt;
&lt;li&gt;Observe the lighthouse on initial load. Note whether it finishes rendering&lt;/li&gt;
&lt;li&gt;Click the theme toggle button once&lt;/li&gt;
&lt;li&gt;Observe the console for WebGL warnings&lt;/li&gt;
&lt;li&gt;Click the toggle four or five more times in quick succession&lt;/li&gt;
&lt;li&gt;Watch the GPU memory in the Performance tab. It climbs instead of staying flat
In production the same steps apply on the Vercel deployment.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Why simplifying the geometry was not the answer
&lt;/h2&gt;

&lt;p&gt;The obvious alternative was to reduce the complexity of the lighthouse. Fewer polygons, simpler materials, basic shapes.&lt;/p&gt;

&lt;p&gt;The problem with that is that a lighthouse made of cylinders and cones with flat shading and no texture is just a cartoon. It would not have represented FaroIQ in any meaningful way. The whole point of building a custom 3D scene instead of a generic hero was that the lighthouse was recognizable as a lighthouse and felt like it belonged to the project.&lt;/p&gt;

&lt;p&gt;Reducing to spheres or abstract geometry would have solved the performance problem by removing the thing that was worth keeping. That is not a fix.&lt;/p&gt;

&lt;p&gt;The actual optimizations that helped performance without compromising the visual were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capping &lt;code&gt;pixelRatio&lt;/code&gt; at 1.5 to avoid running at 3x or 4x on high DPI displays&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;MeshPhongMaterial&lt;/code&gt; instead of &lt;code&gt;MeshStandardMaterial&lt;/code&gt; across the scene, which skips physically based lighting calculations&lt;/li&gt;
&lt;li&gt;Setting segment counts conservatively on each geometry, enough to read as round without subdividing unnecessarily&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;MeshBasicMaterial&lt;/code&gt; for the beam cones and lens sphere since those elements do not need lighting
Those changes made the initial load faster. They did not fix the WebGL context leak.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;p&gt;Before arriving at the &lt;code&gt;key&lt;/code&gt; prop, there were two attempts that seemed reasonable but did not solve the problem.&lt;/p&gt;

&lt;p&gt;The first was moving the cleanup logic into a separate component that would act as a wrapper and handle teardown independently. The idea was that decoupling the renderer lifecycle from the scene setup might give the GPU more breathing room between context changes. In practice it made things worse. The cleanup still happened within the same React tree lifecycle and the WebGL context still accumulated. What it did add was complexity: now the lighthouse logic was split across two components with no clear ownership of the renderer, which made the code harder to reason about for something as specific and self-contained as a 3D scene.&lt;/p&gt;

&lt;p&gt;The second attempt was separating the material definitions from the renderer setup, building the material palette outside the main &lt;code&gt;useEffect&lt;/code&gt; so that only the renderer and scene would re-initialize on theme change while the materials would be shared. This direction had a similar problem. The materials in Three.js are tied to the WebGL context they were created in. Sharing materials across renderer instances does not work the way sharing JavaScript objects does. More importantly, pulling the material definitions out of the component meant scattering the lighthouse configuration across multiple files for a component that was always going to live in one place and serve one purpose. The cognitive cost was not worth it.&lt;/p&gt;

&lt;p&gt;Both attempts were solving the symptom, which was the re-initialization cost, rather than the actual problem, which was the component instance persisting across changes that required a full reset.&lt;/p&gt;




&lt;h2&gt;
  
  
  Memory behavior before and after the fix
&lt;/h2&gt;

&lt;p&gt;Before the fix, the GPU memory pattern in Chrome DevTools was visible without needing exact measurements. Each theme toggle added a layer of memory that did not come back down. The first toggle was usually smooth. By the third or fourth, the page started producing small freezes, the kind where the animation loop stalls for a fraction of a second and the lighthouse beam stops mid-rotation before catching up. By the sixth or seventh toggle in quick succession, the browser tab either threw the WebGL context warning and lost the scene entirely, or became unresponsive and had to be killed.&lt;/p&gt;

&lt;p&gt;The experience was not just a visual problem. Because the lighthouse has a continuous animation loop updating the wave geometry every frame and rotating the beam, a stalled renderer was immediately perceptible. The scene did not degrade gracefully. It either worked or it froze.&lt;/p&gt;

&lt;p&gt;After the fix, the memory line in the performance tab stayed flat across theme switches. The full lighthouse unmounts cleanly, the GPU releases the context, and a fresh instance starts from a clean state. The transition between day and night now feels instant. The beam keeps rotating, the waves keep animating, and the fog density shift from the dense dark mode to the lighter day mode happens without any visible frame drop.&lt;/p&gt;

&lt;p&gt;The difference between the two behaviors is the difference between a WebGL context that accumulates and one that has a defined lifetime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Browser differences
&lt;/h2&gt;

&lt;p&gt;The bug was tested in Chrome and Firefox. Both showed the same core behavior: memory accumulation with each theme toggle and eventual WebGL context loss. Chrome was more explicit about it with the console warning naming the context limit directly. Firefox manifested it more as progressive slowdown and frame drops rather than a hard error, but the underlying cause was identical.&lt;/p&gt;

&lt;p&gt;Safari has a lower WebGL context limit than Chrome, around eight compared to Chrome's sixteen. In a hackathon where judges can open the project on any machine and any browser, that matters. A bug that takes six toggles to crash Chrome might crash Safari on the second or third. The fix applies equally to all of them since it addresses the root cause rather than pushing the limit further away.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;One prop in &lt;code&gt;HeroSection&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LighthouseBackground&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isDark&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;key&lt;/code&gt; prop changes what React does at the component level. Without it, a theme change updates the existing &lt;code&gt;LighthouseBackground&lt;/code&gt; instance. The effect cleanup runs and the effect re-runs in the same component lifetime. The same DOM node, the same refs, the same component.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;key={theme}&lt;/code&gt;, React treats the component as a different element when &lt;code&gt;theme&lt;/code&gt; changes. It fully unmounts the old &lt;code&gt;LighthouseBackground&lt;/code&gt;, running the cleanup and removing it from the tree entirely. The old canvas element is removed from the DOM. The old renderer is disposed. The old component instance is gone. Only then does React mount a fresh &lt;code&gt;LighthouseBackground&lt;/code&gt; with new refs, new state, and a new Three.js scene.&lt;/p&gt;

&lt;p&gt;This gives the browser a genuine boundary between the old renderer's lifetime and the new one's. The GPU context count stays at one. No memory accumulation. No stale context errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// HeroSection.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LighthouseBackground&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isDark&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    ...
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;key&lt;/code&gt; here is &lt;code&gt;theme&lt;/code&gt;, which is the string &lt;code&gt;"dark"&lt;/code&gt; or &lt;code&gt;"light"&lt;/code&gt;. When it changes, React sees a component with a different key and treats it as a complete replacement.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it ended up documented
&lt;/h2&gt;

&lt;p&gt;The fix was intentional enough that it ended up in the project README:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Theme changes force a full remount of the Three.js scene via a React key prop to ensure clean state."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That single sentence captures what took several debug sessions to arrive at. The rest of the theme system description in the README covers what the switch actually does visually: cream and red tower with a bright sky, sun, and ambient light in day mode; dark navy and blue tower with a starfield, moon, and volumetric beam in night mode. The visual difference between modes was significant enough that a partial or broken transition was immediately obvious to anyone looking at the page. That visibility was part of what made fixing it non-negotiable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I took from this
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useEffect&lt;/code&gt; cleanup is not a component teardown. It is a side effect reset within the same component lifetime. For most cases, that distinction is irrelevant. For APIs that hold GPU resources, the difference between updating an instance and replacing it is the difference between a working app and a browser that runs out of WebGL contexts.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;key&lt;/code&gt; prop as a remount trigger is not a workaround. It is the correct React pattern when a prop change requires a full reset of imperative resources rather than an in-place update. Using it intentionally is different from using it as a patch for an architecture problem.&lt;/p&gt;

&lt;p&gt;The performance optimizations around &lt;code&gt;pixelRatio&lt;/code&gt; and material choice were real improvements and should have been there from the start. But they were solving a different problem. A lighter scene still leaks WebGL contexts if the underlying issue is not addressed.&lt;/p&gt;

&lt;p&gt;The lighthouse works now. Theme switching is instant, memory stays flat, and the browser stays alive.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;FaroIQ was built for the Microsoft Agents League Hackathon 2026. Demo available at &lt;a href="https://faroiq.vercel.app" rel="noopener noreferrer"&gt;faroiq.vercel.app&lt;/a&gt;. Source on &lt;a href="https://github.com/carlosjcastro/faroiq" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>The loading screen that took down every browser I opened</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Thu, 20 Aug 2026 00:03:35 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/the-loading-screen-that-took-down-every-browser-i-opened-3pe8</link>
      <guid>https://dev.to/carlosjcastrog/the-loading-screen-that-took-down-every-browser-i-opened-3pe8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a specific kind of bug that makes you question whether you know how to code at all. Not the kind where something throws a clear error and you trace it back in two minutes. The kind where the entire browser window starts flashing black and the only option is to kill the tab before Chrome locks up completely.&lt;/p&gt;

&lt;p&gt;That was the loading screen bug in my portfolio.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project
&lt;/h2&gt;

&lt;p&gt;I had just migrated my personal portfolio to Next.js 16 and was building a more complete version from scratch. The site had a custom loading screen mounted in the root layout. It needed to be there because it was the first thing users saw before any content loaded.&lt;/p&gt;

&lt;p&gt;The loader is not a simple spinner. It draws an SVG path that traces my initial letter G using a stroke animation, moves it to the left, reveals my full name alongside it, and then executes a color wipe exit animation in two stages before calling an &lt;code&gt;onComplete&lt;/code&gt; callback and unmounting. There are refs for direct DOM manipulation of the SVG elements, state variables for the wipe stages, timers, and transition logic layered across all of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;wipe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setWipe&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;wipe2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setWipe2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHidden&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;wipeColor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gPathRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SVGPathElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gWrapperRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SVGGElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textGroupRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SVGGElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svgRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SVGSVGElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timerRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NodeJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standard stuff. Or so I thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was happening
&lt;/h2&gt;

&lt;p&gt;The moment I opened the dev server, the browser window started flashing. Not a subtle flicker. The entire viewport alternating between black and content at full speed, over and over, with no way to stop it without closing the tab. Within seconds, Chrome would throw errors and warnings in the console and start slowing down noticeably. If I left it running, the browser would eventually saturate completely.&lt;/p&gt;

&lt;p&gt;The same thing happened in production. The page stayed black. The loop was executing so fast that the content never had time to render visibly. Anyone visiting the site would see a black screen and nothing else.&lt;/p&gt;

&lt;p&gt;I had to kill the local server every time I opened it just to be able to work on anything else in the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it took time to find
&lt;/h2&gt;

&lt;p&gt;The symptom was so extreme that it pointed in the wrong direction. When a browser behaves like that, the instinct is to look for something major: a memory leak, a broken build configuration, a dependency conflict. I spent time checking all of those and found nothing.&lt;/p&gt;

&lt;p&gt;The component itself looked plausible when I read through it. There was state, there were refs, there was timer logic. No obvious loop anywhere in the code.&lt;/p&gt;

&lt;p&gt;What eventually led me to the actual cause was a combination of two things. I found posts on DEV describing similar symptoms, which pointed me toward infinite render loops as the likely category of problem. Then I opened React DevTools and looked at what was actually happening at runtime. The console was showing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Maximum update depth exceeded. This can happen when a component calls 
setState inside useEffect, but useEffect either doesn't have a dependency 
array, or one of the dependencies changes on every render.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That error, combined with watching the component re-mount continuously in the DevTools component tree, made the cause clear.&lt;/p&gt;

&lt;p&gt;The problem was that state updates controlling the loader behavior were running during render, outside of any effect. In a component with this much timer and transition logic, that is easy to do accidentally. Every render triggered a state change, which triggered another render, which triggered another state change. The browser was re-rendering the entire root layout hundreds of times per second, which is why the viewport was flashing and everything eventually locked up.&lt;/p&gt;

&lt;p&gt;The complexity of the component made it harder to catch on a read-through because the timer logic created the impression that the state updates were conditional and time-bounded. They were not. The condition was being evaluated on every render.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;The solution was two things applied together.&lt;/p&gt;

&lt;p&gt;First, separating the SVG animation logic and the wipe exit logic into two distinct &lt;code&gt;useEffect&lt;/code&gt; hooks, each with explicit cleanup. The SVG animation runs once after mount. The wipe timers run in a separate effect that cleans up all three timeouts on unmount.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SVG draw and reveal, only needs to run once&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gPathRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gWrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gWrapperRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textGroupRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;svgRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&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="nx"&gt;gPath&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;gWrapper&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;textGroup&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTotalLength&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;gPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strokeDasharray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;gPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strokeDashoffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// force reflow so the transition actually fires&lt;/span&gt;
  &lt;span class="nx"&gt;gPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;gPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stroke-dashoffset 1.2s cubic-bezier(0.76, 0, 0.24, 1)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;gPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strokeDashoffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;gWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transform 0.7s cubic-bezier(0.16, 1, 0.3, 1)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;gWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;translateX(0px)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;textGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;opacity 0.5s ease&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;textGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t1&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="c1"&gt;// Exit wipe timers, cleanup on unmount&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setWipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2800&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setWipe2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setHidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;onComplete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;3800&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;timerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;timerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clearTimeout&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="nx"&gt;onComplete&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, I moved the loader into its own isolated component instead of keeping it inline in the root layout. That separation made the lifecycle predictable and prevented any accidental coupling with the state of other components in the layout tree.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;wipeColor&lt;/code&gt; also uses a functional initializer in &lt;code&gt;useState&lt;/code&gt; rather than computing a random value during render:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;wipeColor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;COLORS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters in Next.js because components in the root layout render on the server first. A random value computed directly in the render body would produce a different result on the server versus the client, causing a hydration mismatch on top of the loop problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took from this
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Maximum update depth exceeded&lt;/code&gt; error is one of those React warnings that sounds abstract until you see what it actually produces at runtime. In this case: a completely unusable dev environment and a broken production deployment with a black screen.&lt;/p&gt;

&lt;p&gt;Reading through the code was not enough to catch it. The timer logic created a plausible narrative for why the state updates seemed controlled, and that made the actual problem invisible on a first read. What made the difference was switching from reading the code to watching the runtime behavior in DevTools, combined with finding documentation about similar symptoms.&lt;/p&gt;

&lt;p&gt;Infinite render loops in root layout components are particularly destructive because they take down the entire page, not just the component. A loop in a leaf component somewhere deep in the tree has an isolated impact. In the root layout, nothing works until the loop is gone.&lt;/p&gt;

&lt;p&gt;The loading screen now works exactly as intended. The G traces itself, slides into position, the name appears, the color wipe plays out in two stages, and the page loads cleanly underneath. No flashing, no black screen, no console warnings.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Portfolio built with Next.js 16. Available at &lt;a href="https://carlosjcastrog.com" rel="noopener noreferrer"&gt;carlosjcastrog.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>nextjs</category>
      <category>react</category>
    </item>
    <item>
      <title>Next.js 16 and 16.3, what changed and what to know before upgrading</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Tue, 18 Aug 2026 11:52:33 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/nextjs-16-and-163-what-changed-and-what-to-know-before-upgrading-41f7</link>
      <guid>https://dev.to/carlosjcastrog/nextjs-16-and-163-what-changed-and-what-to-know-before-upgrading-41f7</guid>
      <description>&lt;p&gt;If your project runs Next.js 14 or 15 and you haven't looked at what's changed lately, there's a fair amount to process. Next.js 16 shipped in October 2025 with core changes to the bundler, the React compiler, and the caching model. Version 16.3 reached stable on August 3, 2026 with a redesigned navigation system. Both versions have breaking changes and new capabilities worth understanding before upgrading in production.&lt;/p&gt;

&lt;p&gt;This post covers what changed concretely, what's available by default, what requires manual activation, and when it's worth waiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turbopack is the default since Next.js 16
&lt;/h2&gt;

&lt;p&gt;The most significant thing about version 16 wasn't a new feature, it was Webpack's exit. Turbopack, the Rust-based bundler Vercel had been developing since 2022, became the stable default for both development and production.&lt;/p&gt;

&lt;p&gt;No configuration needed. You upgrade, run &lt;code&gt;next dev&lt;/code&gt;, and you're already using Turbopack. The numbers from production projects are meaningful: builds that took 24.5 seconds dropped to 5.7 seconds. Fast Refresh is up to 10 times faster. Some projects in long development sessions were hitting 21.5 GB of memory before 16.3; the 16.3 release reduced dev server memory usage by up to 90%.&lt;/p&gt;

&lt;p&gt;If you have a custom Webpack configuration, you can still use it with the &lt;code&gt;--webpack&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;next dev &lt;span class="nt"&gt;--webpack&lt;/span&gt;
next build &lt;span class="nt"&gt;--webpack&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you time to migrate without blocking the project. But it's worth doing: Webpack won't receive improvements in future Next.js versions and support is maintenance-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  React Compiler, what it does and when it matters
&lt;/h2&gt;

&lt;p&gt;Next.js 16 included stable support for the React Compiler. What the compiler does is analyze the component tree and add automatic memoization where it detects that a value or component doesn't need to be recalculated. In practice it eliminates most manual &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and &lt;code&gt;memo&lt;/code&gt; calls.&lt;/p&gt;

&lt;p&gt;What it doesn't do is fix poorly written code. If a component has side effects that should be in a &lt;code&gt;useEffect&lt;/code&gt; but are loose in the render body, the compiler doesn't compensate for that. And if your app already has good manual memoization discipline, the perceptible difference can be minimal.&lt;/p&gt;

&lt;p&gt;For new projects it makes sense to enable it from the start. For existing ones, the conservative path is enabling it in staging, measuring the impact, and migrating from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking changes in Next.js 16
&lt;/h2&gt;

&lt;p&gt;There are three changes that can break existing code and are worth reviewing before upgrading.&lt;/p&gt;

&lt;p&gt;Params and searchParams in layouts, pages, and metadata are now Promises. Code that assumed synchronous access to those values will fail. The migration is adding &lt;code&gt;await&lt;/code&gt; before accessing them, or using the official codemod that handles it automatically.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;next/image&lt;/code&gt; changed its defaults: &lt;code&gt;decoding&lt;/code&gt; is now &lt;code&gt;async&lt;/code&gt; by default and &lt;code&gt;fetchPriority&lt;/code&gt; is &lt;code&gt;auto&lt;/code&gt;. Images that relied on the previous behaviors may need explicit attribute adjustments.&lt;/p&gt;

&lt;p&gt;Fetch requests in Server Components that didn't pass a cache policy now default to &lt;code&gt;cache: 'no-store'&lt;/code&gt;. Data that was previously cached silently now gets fetched on every request. If you notice a latency increase after upgrading, that change is likely the reason.&lt;/p&gt;

&lt;p&gt;For migration, Vercel published a codemod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @next/codemod@canary upgrade latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It covers most automatic changes but not all. The official upgrade guide has the detail on what the codemod doesn't handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instant Navigations and Partial Prefetching
&lt;/h2&gt;

&lt;p&gt;Next.js 16.3 reached stable on August 3, 2026. The most significant change is Instant Navigations, which addresses a long-standing gap between Next.js and SPAs: the perceived speed of client-side transitions.&lt;/p&gt;

&lt;p&gt;In Next.js 14 and 15, when a user clicked a link the browser sent a request to the server, waited for the response, and rendered. The wait time was always visible, especially on slower connections. Classic SPAs avoided that by showing immediate content because everything was on the client, but they paid for it in initial load time and SEO.&lt;/p&gt;

&lt;p&gt;Instant Navigations combines two things. Cache Components lets you cache parts of the layout on the client. Partial Prefetching generates a single reusable shell per route and caches it once. If you have 20 links pointing to &lt;code&gt;/products/[id]&lt;/code&gt;, the browser prefetches one generic shell, not 20 individual prefetch requests. When the user clicks, the shell appears immediately while dynamic content arrives from the server.&lt;/p&gt;

&lt;p&gt;The practical result: navigations that feel like those in a SPA without giving up Server Components or the server-first model.&lt;/p&gt;

&lt;p&gt;Both features are opt-in for now. To enable them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cacheComponents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;partialPrefetching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They're planned as defaults in a future major version. Enabling them now isn't a dead end, it's early adoption of something that will become standard.&lt;/p&gt;

&lt;p&gt;Inside routes, you have three options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Stream: shell appears immediately, content arrives after&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Suspense&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Shell&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Content&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Suspense&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Cache: content is cached on the client&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Block: disable Instant Navigations for this specific route&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For fully static prerendered routes with SSG, Instant Navigations doesn't make a perceptible difference. The benefit is highest on dynamic routes with data that varies per request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turbopack and memory in 16.3
&lt;/h2&gt;

&lt;p&gt;Beyond Instant Navigations, 16.3 brought targeted improvements to Turbopack. Dev server memory usage dropped by up to 90% in large projects. Some setups that reached 21.5 GB in long sessions now run on a fraction of that.&lt;/p&gt;

&lt;p&gt;File system caching for builds was also added. Subsequent builds reuse previous work. In large projects that shows up in incremental build time.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP and agents in Next.js 16.3
&lt;/h2&gt;

&lt;p&gt;One addition that's gotten less coverage is the MCP endpoint. Next.js 16.3 exposes &lt;code&gt;/_next/mcp&lt;/code&gt; on the dev server, which allows coding agents to connect to the running server and check the compilation status of specific routes without running a full &lt;code&gt;next build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The route compiler has a &lt;code&gt;compile_route&lt;/code&gt; tool that responds whether a specific route compiles correctly. For AI-assisted development flows, that significantly reduces validation time.&lt;/p&gt;

&lt;p&gt;The Next.js team also shipped four first-party agent skills: one that adopts Cache Components, one that optimizes routes after adoption, one that adopts Partial Prefetching, and a dev-loop skill that connects the agent to the running server through the MCP endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to enable now and what to wait on
&lt;/h2&gt;

&lt;p&gt;For a new project, enabling Turbopack and React Compiler from the start makes sense. Both are stable and the benefits are immediate.&lt;/p&gt;

&lt;p&gt;For Instant Navigations and Partial Prefetching, the decision depends on the application profile. Dynamic routes with many links between pages are the ideal case. Fully static routes won't notice a difference. The sensible path is enabling them in staging, measuring with Lighthouse or Web Vitals, and moving to production with data.&lt;/p&gt;

&lt;p&gt;The breaking changes around async params and the defaults in &lt;code&gt;next/image&lt;/code&gt; and &lt;code&gt;fetch&lt;/code&gt; need review regardless of the rest. The codemod handles most of it but not everything.&lt;/p&gt;

&lt;p&gt;If you use Azure Static Web Apps to deploy your Next.js application, the official Microsoft documentation covers both static and hybrid modes with Server Components. A version migration doesn't require infrastructure configuration changes for most projects:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://learn.microsoft.com/azure/static-web-apps/nextjs?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;https://learn.microsoft.com/azure/static-web-apps/nextjs?wt.mc_id=studentamb_510930&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Information based on official Next.js 16, 16.2, and 16.3 release notes as of August 16, 2026. Next.js may update behaviors between minor versions. Check the official changelog at nextjs.org/blog before upgrading production projects.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
