<?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>Forem: uninterrupted</title>
    <description>The latest articles on Forem by uninterrupted (@engineering).</description>
    <link>https://forem.com/engineering</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3290827%2F5ccc7612-9770-41ed-a766-77b0557e8c3a.jpg</url>
      <title>Forem: uninterrupted</title>
      <link>https://forem.com/engineering</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/engineering"/>
    <language>en</language>
    <item>
      <title>Asset-Based Data Orchestration: Lessons from Building a Multi-State Social Data Platform</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Thu, 26 Mar 2026 07:21:25 +0000</pubDate>
      <link>https://forem.com/u11d/asset-based-data-orchestration-lessons-from-building-a-multi-state-social-data-platform-9l</link>
      <guid>https://forem.com/u11d/asset-based-data-orchestration-lessons-from-building-a-multi-state-social-data-platform-9l</guid>
      <description>&lt;p&gt;Building reliable data platforms rarely fails because of scale alone.&lt;br&gt;
More often, reliability collapses under &lt;strong&gt;heterogeneity&lt;/strong&gt;: multiple data providers, inconsistent schemas, partial updates, and unclear ownership.&lt;/p&gt;

&lt;p&gt;While building a multi-state social data platform ingesting resource data from dozens of organizations, we discovered that reliability is not a property of pipelines. It is a property of &lt;strong&gt;data artifacts and their relationships&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Why Reliability Becomes a Systems Problem at Scale
&lt;/h2&gt;

&lt;p&gt;The accompanying essay frames trust as something earned through consistent system behavior under real-world pressure. What that framing leaves implicit, but what became unavoidable in practice, is that reliability stops being a property of individual components very early on. Once multiple organizations, jurisdictions, and publishing surfaces are involved, reliability becomes an emergent property of the &lt;em&gt;entire system&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For us, this meant that no single pipeline, connector, or database could be made "reliable enough" in isolation. Failures were rarely total. They were partial, localized, and often silent. The engineering challenge was not preventing all failure, but designing the system so that failures were isolated, detectable, and explainable.&lt;/p&gt;

&lt;p&gt;That requirement drove nearly every architectural decision that followed.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. System Overview
&lt;/h2&gt;

&lt;p&gt;Technically, the platform ingests social service resource data from independent organizations operating across multiple U.S. states. Each organization exposes data via a different source system (for example iCarol, WellSky, VisionLink, RTM), with varying schemas, update cadences, and quality guarantees.&lt;/p&gt;

&lt;p&gt;At a high level, the system consists of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Writers&lt;/strong&gt; - per-tenant ingestion and transformation projects that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch raw data from source systems via connector adapters&lt;/li&gt;
&lt;li&gt;Persist raw data into Snowflake source schemas&lt;/li&gt;
&lt;li&gt;Normalize and standardize data via DBT&lt;/li&gt;
&lt;li&gt;Apply enhancements such as geocoding or translations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Readers&lt;/strong&gt; - publisher processes that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React to completed writer runs, either bulk or incremental&lt;/li&gt;
&lt;li&gt;Publish curated artifacts to OpenSearch, MongoDB, and optionally Postgres&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Snowflake acts as the system of record for intermediate and normalized datasets. Dagster coordinates the execution and materialization of data assets. DBT is used explicitly for set-based transformations, not orchestration.&lt;/p&gt;

&lt;p&gt;Scale is not extreme in raw volume, but complexity is high: dozens of tenants, hundreds of tables per tenant, and frequent partial updates.&lt;/p&gt;
&lt;h3&gt;
  
  
  High-Level Architecture
&lt;/h3&gt;

&lt;p&gt;The following diagram illustrates the writer → Snowflake → DBT → asset orchestration → readers pattern.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmvl4kvl30b7i0uowsfj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmvl4kvl30b7i0uowsfj.webp" alt="Asset based data orchestration high level architecture" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The platform is designed around &lt;strong&gt;asset lineage and normalization contracts&lt;/strong&gt;, not pipelines.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Source Heterogeneity as the Dominant Constraint
&lt;/h2&gt;

&lt;p&gt;The hardest constraint was not throughput or storage. It was heterogeneity.&lt;/p&gt;

&lt;p&gt;Each data provider differed along several axes simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema shape&lt;/strong&gt;: even when nominally "the same" entities existed, fields varied&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantics&lt;/strong&gt;: identical fields often meant subtly different things&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update cadence&lt;/strong&gt;: some sources updated continuously, others weekly or ad hoc&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality guarantees&lt;/strong&gt;: missing fields, stale records, or partial exports were common&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Early on, we underestimated how strongly these differences would dominate system design. Attempting to treat ingestion as a uniform, pipeline-shaped process led to brittle assumptions and cross-tenant coupling.&lt;/p&gt;

&lt;p&gt;The system only became manageable once heterogeneity was treated as &lt;em&gt;fundamental&lt;/em&gt;, not incidental.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Normalization (HSDS, SDOH or Equivalent) as an Architectural Contract
&lt;/h2&gt;

&lt;p&gt;Normalization into an HSDS-like model was not implemented as a downstream convenience. It became an architectural contract.&lt;/p&gt;

&lt;p&gt;All downstream consumers, internal and external, implicitly rely on the guarantees of the normalized model: stable fields, predictable relationships, and documented semantics. That meant normalization could not be "best effort" or delayed until the end of a pipeline.&lt;/p&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raw source data is written verbatim into Snowflake source schemas&lt;/li&gt;
&lt;li&gt;DBT ELT projects transform this raw data into standardized intermediate models&lt;/li&gt;
&lt;li&gt;DBT STAGE projects apply tenant-specific adaptations while preserving the contract&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation made it explicit where interpretation happens. If a field is wrong in the normalized model, the question becomes &lt;em&gt;which contract was violated&lt;/em&gt;, not "what broke in the pipeline".&lt;/p&gt;
&lt;h2&gt;
  
  
  5. What We Got Wrong Initially
&lt;/h2&gt;

&lt;p&gt;Several early assumptions did not survive contact with reality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline-first thinking&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We initially modeled work as long-running jobs. This obscured which intermediate datasets were durable, reusable, or safe to depend on. Debugging often meant rerunning more than necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual validation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Data quality checks lived outside the orchestration layer. Engineers and analysts manually inspected outputs, which worked at small scale but failed under concurrency and time pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared failure domains&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Multiple tenants often shared execution paths. A failure in one tenant’s ingestion could block or delay others, even when their data was unrelated.&lt;/p&gt;

&lt;p&gt;None of these issues were catastrophic individually. Together, they made reliability depend on human attention.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Transitioning to Asset-Based Data Orchestration with Dagster
&lt;/h2&gt;

&lt;p&gt;The shift to asset-based orchestration was driven less by tooling preference and more by a change in mental model.&lt;/p&gt;

&lt;p&gt;Instead of asking "what jobs should run?", we started asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What data artifacts must exist?&lt;/li&gt;
&lt;li&gt;What do they depend on?&lt;/li&gt;
&lt;li&gt;How fresh do they need to be?&lt;/li&gt;
&lt;li&gt;What constitutes success or failure for &lt;em&gt;this&lt;/em&gt; artifact?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dagster assets provided a way to encode those questions directly.&lt;/p&gt;

&lt;p&gt;A simplified example from a writer project shows how DBT models are treated as assets rather than opaque steps:&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;# writer-xyz/assets.py (excerpt)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dagster_dbt&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_assets_from_dbt_project&lt;/span&gt;

&lt;span class="n"&gt;dbt_elt_assets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_assets_from_dbt_project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;project_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dbt_elt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;profiles_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dbt_elt&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not explain how DBT runs. It declares that the resulting tables are first-class assets with lineage and state.&lt;/p&gt;

&lt;p&gt;Once assets replaced jobs as the primary abstraction, freshness, lineage, and partial recomputation became explicit rather than implicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Partitioning for Failure Isolation
&lt;/h2&gt;

&lt;p&gt;Partitioning was critical for isolating failures.&lt;/p&gt;

&lt;p&gt;We partitioned primarily along tenant and state boundaries, not time. This reflected operational reality: data issues almost always affected a single organization or region.&lt;/p&gt;

&lt;p&gt;In Dagster terms, this meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separate writer projects per tenant&lt;/li&gt;
&lt;li&gt;Independent schedules and sensors&lt;/li&gt;
&lt;li&gt;Asset materializations scoped to a tenant’s data domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A failure in one writer no longer blocked publishing for others. More importantly, remediation could be targeted and auditable.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Data Quality Embedded in the Asset Graph
&lt;/h2&gt;

&lt;p&gt;Data validation moved into the asset graph itself.&lt;/p&gt;

&lt;p&gt;Instead of post-hoc checks, validations became explicit dependencies. If a validation asset failed, downstream assets simply did not materialize.&lt;/p&gt;

&lt;p&gt;An example pattern used across writers:&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="nd"&gt;@asset&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_staging_tables&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;staging_tables&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;staging_tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count_missing_ids&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally simple. The key point is not the check itself, but that failure is structural. The system records that an expected artifact does not exist, rather than silently publishing bad data.&lt;/p&gt;

&lt;p&gt;This shifted failure detection earlier and reduced the blast radius of errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Operational Outcomes
&lt;/h2&gt;

&lt;p&gt;Day-to-day operations changed in several concrete ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On-call work shifted from rerunning pipelines to inspecting asset lineage&lt;/li&gt;
&lt;li&gt;Partial backfills became routine rather than exceptional&lt;/li&gt;
&lt;li&gt;Publishing delays were easier to attribute to specific upstream causes&lt;/li&gt;
&lt;li&gt;New tenants could be added without increasing shared operational risk&lt;/li&gt;
&lt;li&gt;None of this eliminated operational effort. It made that effort more focused and less reactive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Open Trade-offs and Unresolved Questions
&lt;/h2&gt;

&lt;p&gt;Some challenges remain unresolved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross-tenant schema evolution still requires coordination and discipline&lt;/li&gt;
&lt;li&gt;Observability across Snowflake, DBT, Dagster, and downstream stores is fragmented&lt;/li&gt;
&lt;li&gt;Cost attribution at the asset level is still coarse-grained&lt;/li&gt;
&lt;li&gt;Human review remains necessary for certain semantic validations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With more time, we would invest earlier in unified observability and more formal schema versioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Why These Lessons Matter Beyond This Platform
&lt;/h2&gt;

&lt;p&gt;These lessons are not unique to this system.&lt;/p&gt;

&lt;p&gt;Any platform operating in civic tech, govtech, or environmental data shares similar constraints: multiple data producers, uneven quality, and real-world consequences for failure.&lt;/p&gt;

&lt;p&gt;The core takeaway is not "use asset-based orchestration", but treat data artifacts as obligations. Once that shift happens, many architectural decisions become clearer.&lt;/p&gt;

&lt;p&gt;Reliability stops being something you hope for and becomes something you can reason about.&lt;/p&gt;

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

&lt;p&gt;The biggest lesson from this platform was not about any particular tool. It was about how we model the system itself.&lt;/p&gt;

&lt;p&gt;Once data artifacts became the core abstraction, many reliability problems became easier to reason about. Failures became visible, dependencies became explicit, and operational work shifted from firefighting pipelines to managing data contracts.&lt;/p&gt;

&lt;p&gt;For data platforms operating across heterogeneous sources, this shift can be the difference between a system that merely runs - and one that can be trusted.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is asset-based data orchestration?
&lt;/h3&gt;

&lt;p&gt;Asset-based data orchestration treats data artifacts (tables, datasets, models) as the primary units of orchestration instead of pipelines or jobs. Systems like Dagster allow teams to define dependencies between assets, enabling better lineage tracking, partial recomputation, and failure isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is asset-based orchestration useful for complex data platforms?
&lt;/h3&gt;

&lt;p&gt;In large systems with many data producers and consumers, failures are rarely binary. Asset-based orchestration makes dependencies explicit, allowing teams to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;isolate failures&lt;/li&gt;
&lt;li&gt;recompute only affected datasets&lt;/li&gt;
&lt;li&gt;track lineage across transformations&lt;/li&gt;
&lt;li&gt;enforce data quality checks before publishing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How does Dagster differ from traditional pipeline orchestrators?
&lt;/h3&gt;

&lt;p&gt;Traditional orchestrators schedule jobs or workflows. Dagster emphasizes data assets and lineage. This enables better visibility into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what datasets exist&lt;/li&gt;
&lt;li&gt;what produced them&lt;/li&gt;
&lt;li&gt;what depends on them&lt;/li&gt;
&lt;li&gt;whether they meet freshness or quality requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When should teams adopt asset-based orchestration?
&lt;/h3&gt;

&lt;p&gt;Asset-based orchestration becomes particularly valuable when systems have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;many data sources&lt;/li&gt;
&lt;li&gt;heterogeneous schemas&lt;/li&gt;
&lt;li&gt;multiple downstream consumers&lt;/li&gt;
&lt;li&gt;partial or incremental updates&lt;/li&gt;
&lt;li&gt;strict reliability requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These conditions are common in &lt;strong&gt;multi-tenant data platforms and civic tech systems&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does asset-based orchestration replace tools like DBT?
&lt;/h3&gt;

&lt;p&gt;No. Asset-based orchestration &lt;strong&gt;complements transformation tools&lt;/strong&gt; like DBT. In many architectures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DBT performs &lt;strong&gt;set-based transformations&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Dagster manages &lt;strong&gt;asset lineage, dependencies, scheduling, and validation&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation keeps orchestration and transformation responsibilities clear.&lt;/p&gt;

</description>
      <category>dagster</category>
      <category>dataorchestration</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Next.js 16 Partial Prerendering (PPR): The Best of Static and Dynamic Rendering</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Thu, 05 Mar 2026 08:21:46 +0000</pubDate>
      <link>https://forem.com/u11d/nextjs-16-partial-prerendering-ppr-the-best-of-static-and-dynamic-rendering-2fgg</link>
      <guid>https://forem.com/u11d/nextjs-16-partial-prerendering-ppr-the-best-of-static-and-dynamic-rendering-2fgg</guid>
      <description>&lt;p&gt;Next.js has long been a leader in giving developers flexible, high-performance rendering strategies - &lt;a href="https://u11d.com/blog/ssg-isr-ssr-csr-which-strategy-should-i-use-in-my-next-js-e-commerce-platform/" rel="noopener noreferrer"&gt;Static Site Generation (SSG), Server-Side Rendering (SSR), Incremental Static Regeneration (ISR), and Client-Side Rendering (CSR)&lt;/a&gt; all play roles depending on your use case. With Next.js 14, Partial Prerendering (PPR) was introduced as an experimental feature, starting from Next.js 16, PPR has become a stable and becomes a foundational part of this landscape by letting you &lt;em&gt;blend static pre-rendering and dynamic behavior in the same route&lt;/em&gt;, improving perceived performance and SEO without sacrificing real-time data needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Is Partial Prerendering (PPR)?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Partial Prerendering&lt;/strong&gt; is a rendering strategy that lets Next.js deliver a &lt;strong&gt;static HTML “shell” immediately&lt;/strong&gt;, then &lt;em&gt;stream in dynamic content&lt;/em&gt; as it becomes available - &lt;em&gt;all in the same server response&lt;/em&gt;. Instead of choosing static &lt;em&gt;or&lt;/em&gt; dynamic at the page level, PPR combines both on a per-component basis.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;static shell&lt;/strong&gt; (layout, logo, product titles, navigation) is pre-generated at build time or cached ahead of requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic parts&lt;/strong&gt; (cart state, recommendations, session data) load later and are &lt;em&gt;streamed&lt;/em&gt; to the client using React’s &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; boundaries.&lt;/li&gt;
&lt;li&gt;Users see meaningful UI immediately, with dynamic content filling in seamlessly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to &lt;em&gt;faster Time to First Byte (TTFB)&lt;/em&gt; and a smoother perceived experience, while still allowing data personalization and real-time updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How Partial Prerendering Works in Next.js 16&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cache Components: The Heart of PPR&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In Next.js 16, PPR isn’t just an experimental flag - it’s integrated into the &lt;a href="https://nextjs.org/docs/app/getting-started/cache-components" rel="noopener noreferrer"&gt;&lt;strong&gt;Cache Components&lt;/strong&gt;&lt;/a&gt; system. Cache Components lets you &lt;em&gt;opt-in to cache certain components or data instead of rendering them on every request&lt;/em&gt;, making pre-rendering more predictable and performant.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cacheComponents: true&lt;/code&gt;&lt;/strong&gt; in &lt;code&gt;next.config.ts&lt;/code&gt; enables PPR and component-level caching.&lt;/li&gt;
&lt;li&gt;The new &lt;a href="https://nextjs.org/docs/app/getting-started/cache-components#during-prerendering" rel="noopener noreferrer"&gt;&lt;code&gt;"use cache"&lt;/code&gt;&lt;/a&gt; directive lets you mark data-fetching functions or components as cacheable.&lt;/li&gt;
&lt;li&gt;Anything not cacheable or depending on request-specific data can be wrapped in a &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; fallback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach gives you fine-grained control: static UI is reused across requests, while dynamic parts rehydrate or stream in only when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Streaming and Suspense: The Engine Under the Hood&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;React’s &lt;strong&gt;&lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;&lt;/strong&gt; plays a key role in PPR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrap dynamic components in &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; with a fallback UI (like a skeleton loader).&lt;/li&gt;
&lt;li&gt;During rendering, Suspense tells Next.js where to &lt;em&gt;halt pre-rendering&lt;/em&gt; and stream dynamic content later.&lt;/li&gt;
&lt;li&gt;The server sends the pre-rendered shell immediately and then streams dynamic sections in parallel as they’re ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This strategy avoids blocks in page delivery and reduces the “white screen” effect often seen in traditional SSR or CSR approaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why PPR Matters for E-commerce&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For e-commerce platforms, performance directly impacts sales and SEO - factors also emphasized in the U11D articles about rendering strategies. A slow page can hurt conversions and search rankings. Partial Prerendering improves this by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster initial loads:&lt;/strong&gt; Users see the page skeleton instantly, improving engagement and performance metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO advantages:&lt;/strong&gt; Static content is available for crawler indexing immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic personalization:&lt;/strong&gt; Cart contents, recommendations, user-specific prices or availability can appear without blocking the initial render.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More flexible than SSG/SSR alone:&lt;/strong&gt; You’re not limited to fully static or completely dynamic pages - the best of both lives together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In e-commerce apps where product detail pages might be static for most users but show personalized recommendations or inventory status, PPR is a compelling hybrid.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to Enable and Use PPR in Next.js 16&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable Cache Components:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;next.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&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="c1"&gt;// enables Partial Prerendering&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;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wrap dynamic UI:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use React’s &lt;code&gt;Suspense&lt;/code&gt; for dynamic content:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight tsx"&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;Suspense&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;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&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="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Header&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;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&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;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading product...&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;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="nc"&gt;ProductDetails&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;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&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;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;use cache&lt;/code&gt; for predictable dynamic data:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Inside dynamic data functions, prefix with &lt;code&gt;"use cache"&lt;/code&gt; to mark them cacheable:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&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="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProduct&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;/code&gt;&lt;/pre&gt;


&lt;p&gt;This tells Next.js that caching is safe for that data.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How PPR Fits with Other Rendering Strategies&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Partial Prerendering doesn’t replace SSR, SSG, ISR, or CSR - but &lt;em&gt;coordinates with them&lt;/em&gt;. While U11D’s articles give a great overview of traditional strategies like SSG, SSR, and ISR, PPR adds a hybrid strategy that sits between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSG/ISR:&lt;/strong&gt; Good for full static pages or pages that regenerate occasionally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR:&lt;/strong&gt; Ideal for real-time personalized content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSR:&lt;/strong&gt; Great for highly interactive client-heavy UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PPR (Next.js 16):&lt;/strong&gt; Combines static cached shells with streaming dynamic content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of PPR as a &lt;em&gt;component-level SSG + dynamic hybrid -&lt;/em&gt; fast to load like SSG, flexible like SSR.&lt;/p&gt;

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

&lt;p&gt;Next.js 16 elevates Partial Prerendering from an experimental concept to a practical, integrated performance strategy through &lt;strong&gt;Cache Components&lt;/strong&gt; and React Suspense. It’s especially powerful for complex, dynamic sites like e-commerce stores, where you want the benefits of static pre-rendering &lt;em&gt;and&lt;/em&gt; dynamic personalization without sacrificing UX or SEO.&lt;/p&gt;

&lt;p&gt;By serving instantly usable HTML and streaming dynamic parts in parallel, PPR bridges the traditional divide between static and dynamic rendering - helping your app &lt;em&gt;feel&lt;/em&gt; faster while staying robust and scalable.&lt;/p&gt;

&lt;p&gt;If you’re building with Next.js 16, definitely explore PPR alongside other strategies like SSR, ISR, and CSR to find the most performance-optimized combination for your routes.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ppr</category>
    </item>
    <item>
      <title>Using Proxy (before Middleware) in Next.js: a modern layer</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Wed, 26 Nov 2025 07:00:00 +0000</pubDate>
      <link>https://forem.com/u11d/using-proxy-before-middleware-in-nextjs-a-modern-layer-1iik</link>
      <guid>https://forem.com/u11d/using-proxy-before-middleware-in-nextjs-a-modern-layer-1iik</guid>
      <description>&lt;p&gt;&lt;a href="https://nextjs.org/blog/next-16" rel="noopener noreferrer"&gt;Next.js 16&lt;/a&gt; introduces a new file-convention: &lt;code&gt;proxy.js&lt;/code&gt; / &lt;code&gt;proxy.ts&lt;/code&gt; (often “Proxy”) which supersedes the older &lt;code&gt;middleware.js&lt;/code&gt;. This feature allows you to intercept HTTP requests early (before routing/rendering) and run custom logic—redirects, rewrites, header manipulation, or forwarding (proxying) to another service.&lt;/p&gt;

&lt;p&gt;In e-commerce (or any frontend/back-end scenario) this gives you a powerful tool: you can make your Next.js app act as a &lt;strong&gt;Backend-for-Frontend (BFF)&lt;/strong&gt; layer, hiding upstream APIs, consolidating micro-services, enforcing auth, caching, rewriting URLs, etc. This aligns well with many of the architectures discussed in your referenced articles about Next.js for commerce (SSG/ISR/SSR strategies etc).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Proxy and when to use it
&lt;/h2&gt;

&lt;p&gt;The Proxy file (&lt;code&gt;proxy.js&lt;/code&gt;/&lt;code&gt;.ts&lt;/code&gt;) in Next.js is a special server-side file (located in the project root or under &lt;code&gt;src&lt;/code&gt;) that runs &lt;em&gt;before&lt;/em&gt; any route handling or page rendering occurs. In fact, Proxy (and Middleware) executes before &lt;strong&gt;every&lt;/strong&gt; incoming request — not just those for pages or APIs — including requests for assets like images, icons, or JavaScript files. Because of this, Next.js provides the &lt;code&gt;matcher&lt;/code&gt; configuration, allowing you to specify which routes the Proxy should apply to and avoid intercepting unnecessary requests.&lt;/p&gt;

&lt;p&gt;It allows you to inspect the incoming &lt;code&gt;request&lt;/code&gt;, then respond, redirect, rewrite, or modify headers based on your application’s needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relation to Middleware &amp;amp; Rewrites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In previous versions, &lt;code&gt;middleware.js&lt;/code&gt; (App Router) was used for this pattern; Proxy replaces it.&lt;/li&gt;
&lt;li&gt;While &lt;code&gt;rewrites()&lt;/code&gt; in &lt;code&gt;next.config.js&lt;/code&gt; can act like a proxy by mapping URL paths to &lt;a href="https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites" rel="noopener noreferrer"&gt;different destinations&lt;/a&gt;, the Proxy feature goes further — it gives you direct access to the &lt;strong&gt;request object itself&lt;/strong&gt;, including headers, cookies, and other request data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to use Proxy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Proxy works best in scenarios that require lightweight, real-time adjustments to incoming requests — such as performing quick redirects based on request details, dynamically rewriting routes for A/B testing or experimental features, or modifying request and response headers across specific routes or the entire application.&lt;/p&gt;

&lt;p&gt;However, it’s not well-suited for tasks involving heavy data fetching or complex session management, as those are better handled by dedicated API routes or backend services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relation to Backend-for-Frontend (BFF) pattern&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the BFF pattern, the frontend has a dedicated backend layer (the “for the frontend”) that aggregates, transforms, or proxies requests to multiple microservices. Next.js supports this pattern via Route Handlers, Proxy and server actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Proxy: conventions &amp;amp; example
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File convention&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;proxy.ts&lt;/code&gt; (or &lt;code&gt;proxy.js&lt;/code&gt;) file in the &lt;strong&gt;project root&lt;/strong&gt; or inside &lt;code&gt;src/&lt;/code&gt; alongside your top‐level &lt;code&gt;app/&lt;/code&gt; or &lt;code&gt;pages/&lt;/code&gt;folder.&lt;/li&gt;
&lt;li&gt;Only one proxy file per project is supported—though you can modularize logic within it by importing helpers.&lt;/li&gt;
&lt;li&gt;Export a function named &lt;code&gt;proxy&lt;/code&gt; (or default) that receives a &lt;code&gt;NextRequest&lt;/code&gt; and returns a &lt;code&gt;NextResponse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Optionally export a &lt;code&gt;config&lt;/code&gt; object with a &lt;code&gt;matcher&lt;/code&gt; property to specify which routes this proxy applies to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Basic example&lt;/strong&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="c1"&gt;// proxy.ts&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;NextResponse&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/server&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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// simple redirect&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/about/:path*&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This intercepts &lt;code&gt;/about/*&lt;/code&gt; and sends users to &lt;code&gt;/home&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proxying to different pathname&lt;/strong&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="c1"&gt;// proxy.ts&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;NextResponse&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/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/docs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v2/docs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way &lt;code&gt;/v1/docs&lt;/code&gt; in your Next.js app becomes a proxy to your &lt;code&gt;v2&lt;/code&gt; version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important flags &amp;amp; matching&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;matcher&lt;/code&gt; can be a string, array of strings, or objects with &lt;code&gt;source&lt;/code&gt;, &lt;code&gt;has&lt;/code&gt;, &lt;code&gt;missing&lt;/code&gt;, &lt;code&gt;regexp&lt;/code&gt; for fine control.&lt;/li&gt;
&lt;li&gt;You should exclude internal Next.js paths such as &lt;code&gt;/_next/&lt;/code&gt;, static assets etc, to avoid intercepting everything accidentally.&lt;/li&gt;
&lt;li&gt;Be aware of runtime: The Proxy is executed by default in the Edge Runtime environment; performance matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use cases &amp;amp; how it fits e-commerce / frontend/back-end scenarios
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use case 1: Authentication / session gating before forwarding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before forwarding to upstream API, you can inspect cookies/headers in Proxy, validate session, add or strip auth headers, or reject unauthorized requests early. This centralises “frontend backend” logic.&lt;/p&gt;

&lt;p&gt;For example:&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="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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sessionToken&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Use case 2: A/B testing, feature flags, routing to different backends&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;E-commerce often runs experiments: you might want to route certain requests to a “new checkout backend” or “legacy order API” based on a feature flag. Proxy allows dynamic routing:&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;featureFlag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newCheckout&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;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/checkout&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rewrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new-checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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 is exactly where Proxy shines, beyond simple rewrites. The docs mention this scenario.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use case 3: Multi-zone / micro-frontend routing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you break your site into zones (e.g., &lt;code&gt;/shop&lt;/code&gt;, &lt;code&gt;/blog&lt;/code&gt;, &lt;code&gt;/dashboard&lt;/code&gt;) and each is a separate Next.js app or separate backend service, you can use Proxy to route to the correct service based on path or condition. The docs mention this under Multi-Zones: “Proxy can also be used when there is a need for a dynamic decision when routing.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How this complements SSG/ISR/SSR strategies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your e-commerce context (as your referenced articles explore SSG/ISR/SSR), Proxy supports architecture by giving you control over how client-requests hit your backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you statically generate product pages (SSG/ISR) but still need dynamic APIs behind the scenes (inventory, pricing), Proxy sits in front of those APIs.&lt;/li&gt;
&lt;li&gt;If you use SSR for checkout or account pages, you might fetch via your BFF rather than directly from client; Proxy gives you that layer.&lt;/li&gt;
&lt;li&gt;This allows you to &lt;strong&gt;decouple&lt;/strong&gt; the frontend build (Next.js) from the backend services, while still providing unified domain, consistent cookies/sessions, and reduction of client-side complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The Proxy file convention in Next.js is a modern, flexible tool for intercepting HTTP requests early in your application lifecycle. It’s especially valuable in setups where your frontend is part of a larger ecosystem (microservices, e-commerce, feature-flagged experiments) and you need a lightweight backend-for-frontend layer without building a full custom server.&lt;/p&gt;

&lt;p&gt;For e-commerce platforms built with Next.js—where you may have static product pages, dynamic inventory/pricing APIs, customer sessions, checkout flows—the Proxy gives you an elegant place to manage domain-consolidation, routing, auth, A/B experimentation and API facade logic.&lt;/p&gt;

&lt;p&gt;However, you should use it wisely: keep logic lean, use it for the right cases (redirects, rewrites, header manipulation, request forwarding) rather than heavy data aggregation. Combined with Next.js’s rendering strategies (SSG, ISR, SSR) and BFF pattern, Proxy becomes an important architectural building block.&lt;/p&gt;

</description>
      <category>nextjs</category>
    </item>
    <item>
      <title>Automated Tax Compliance for Global E-Commerce Growth</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Mon, 17 Nov 2025 08:06:29 +0000</pubDate>
      <link>https://forem.com/u11d/automated-tax-compliance-for-global-e-commerce-growth-5g8b</link>
      <guid>https://forem.com/u11d/automated-tax-compliance-for-global-e-commerce-growth-5g8b</guid>
      <description>&lt;p&gt;Choosing an e-commerce platform today means choosing your capabilities for the next 5-10 years. Most businesses focus on features and design, overlooking a critical question: can this platform handle tax compliance as we grow, regulations change, and new sales channels emerge?&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Compliance Challenges That Break Legacy Platforms
&lt;/h2&gt;

&lt;p&gt;Real-time performance matters more than you think. Every millisecond at checkout affects conversion. Legacy tax calculation systems add 300-500ms latency per request — enough to impact sales. Worse, they often fail silently during high-traffic periods, forcing you to choose between lost sales or compliance risk.&lt;/p&gt;

&lt;p&gt;Headless architecture becomes critical as your business evolves. Selling through mobile apps, marketplaces, social commerce, and IoT devices means your tax system needs API-first design. Shopify's monolithic architecture forces you into their frontend. WooCommerce wasn't built for headless operations. Magento's complexity makes omnichannel integration expensive.&lt;/p&gt;

&lt;p&gt;Audit readiness isn't optional. Tax authorities increasingly audit e-commerce businesses, and "we did our best" isn't a defense. You need complete transaction history, documentation of every tax decision, and proof that calculations were accurate at transaction time. Most platforms treat this as an afterthought, storing minimal data that won't satisfy auditors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Performance-Compliance Tradeoff (That Shouldn't Exist)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.avalara.com/us/en/index.html" rel="noopener noreferrer"&gt;Avalara's&lt;/a&gt; real-time API calculates taxes in under 100ms, handling peak traffic without degradation. This isn't just about speed — it's about reliability. During Black Friday traffic spikes, tax calculations don't become a bottleneck.&lt;/p&gt;

&lt;p&gt;Combined with Medusa's modern architecture, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sub-second checkout experiences with real-time tax accuracy&lt;/li&gt;
&lt;li&gt;Async tax calculation options for ultra-high-volume scenarios&lt;/li&gt;
&lt;li&gt;Automatic failover and retry logic for maximum reliability&lt;/li&gt;
&lt;li&gt;Complete transaction logging for audit trails&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Technical Foundation Matters
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://medusajs.com/" rel="noopener noreferrer"&gt;Medusa's&lt;/a&gt; API-first, headless design means tax compliance works consistently across every channel. Selling through your website, mobile app, and Amazon? Same tax logic, same accuracy, same audit trail — without custom integration for each channel.&lt;/p&gt;

&lt;p&gt;This architecture also enables sophisticated business rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer-specific tax exemptions applied automatically across all channels&lt;/li&gt;
&lt;li&gt;Dynamic tax calculation based on real-time inventory location&lt;/li&gt;
&lt;li&gt;Split fulfillment from multiple warehouses with correct origin-based taxation&lt;/li&gt;
&lt;li&gt;International expansion without rebuilding your tax infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building Audit-Ready From Day One
&lt;/h2&gt;

&lt;p&gt;When tax authorities audit your business, they want answers to specific questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How did you determine tax rates for each transaction?&lt;/li&gt;
&lt;li&gt;Where are exemption certificates for tax-exempt sales?&lt;/li&gt;
&lt;li&gt;How do you handle returns and refunds?&lt;/li&gt;
&lt;li&gt;What's your documentation for nexus determination?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/@u11d/medusa-avalara" rel="noopener noreferrer"&gt;Medusa-Avalara integration&lt;/a&gt; maintains complete records automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every tax calculation with jurisdiction breakdown&lt;/li&gt;
&lt;li&gt;Customer exemption certificates with validation status&lt;/li&gt;
&lt;li&gt;Transaction lifecycle (calculated → committed → voided/refunded)&lt;/li&gt;
&lt;li&gt;Address validation records proving due diligence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're not preparing for audits — you're always ready. Export complete documentation on demand, proving compliance for every transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Platform Investment Decision
&lt;/h2&gt;

&lt;p&gt;Legacy platforms force tradeoffs: Shopify is easy but inflexible. Magento is powerful but expensive and complex. WooCommerce is customizable but scales poorly.&lt;/p&gt;

&lt;p&gt;Medusa eliminates these tradeoffs: open-source flexibility, modern architecture, enterprise capabilities without enterprise costs. When paired with Avalara's tax automation, you get infrastructure that grows with your business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scale Without Technical Debt
&lt;/h2&gt;

&lt;p&gt;Most e-commerce businesses outgrow their first platform. They've built workarounds for limitations, accumulated technical debt, and face expensive migration projects. This is avoidable.&lt;/p&gt;

&lt;p&gt;Starting with proper infrastructure—headless architecture, real-time tax automation, complete compliance documentation—means your platform grows with you instead of holding you back.&lt;/p&gt;

&lt;p&gt;Tax compliance isn't exciting, but it's foundational. Get it right from the start, and it becomes invisible infrastructure. Get it wrong, and it becomes an expensive barrier to growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Need help with Avalara integration?
&lt;/h2&gt;

&lt;p&gt;Looking to implement automated tax compliance with Avalara in your Medusa store? u11d specializes in e-commerce integrations and tax automation solutions. We can help you set up, customize, and optimize your Avalara integration for maximum compliance and efficiency. Our team has extensive experience with both Medusa e-commerce platforms and Avalara tax systems, ensuring seamless integration that meets your specific business requirements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://u11d.com/contact/" rel="noopener noreferrer"&gt;Contact Us&lt;/a&gt;&lt;/p&gt;

</description>
      <category>avalara</category>
      <category>medusa</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>A Practical Guide to Scaling Medusa with Kubernetes Autoscalers</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Wed, 29 Oct 2025 09:56:36 +0000</pubDate>
      <link>https://forem.com/u11d/a-practical-guide-to-scaling-medusa-with-kubernetes-autoscalers-4hmj</link>
      <guid>https://forem.com/u11d/a-practical-guide-to-scaling-medusa-with-kubernetes-autoscalers-4hmj</guid>
      <description>&lt;p&gt;As your &lt;strong&gt;Medusa.js e-commerce platform&lt;/strong&gt; grows, performance and reliability depend on how well it scales under load. Kubernetes provides native tools like the &lt;strong&gt;Horizontal Pod Autoscaler (HPA)&lt;/strong&gt; and &lt;strong&gt;KEDA&lt;/strong&gt; to automatically adjust resources based on real-time demand.&lt;br&gt;
In this guide, you’ll learn how to configure &lt;strong&gt;Medusa for horizontal scaling in Kubernetes&lt;/strong&gt;, using &lt;strong&gt;Prometheus&lt;/strong&gt;, &lt;strong&gt;cAdvisor&lt;/strong&gt;, and &lt;strong&gt;HPA&lt;/strong&gt; - ensuring your store remains responsive even during peak traffic periods.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites for Medusa Autoscaling on Kubernetes
&lt;/h2&gt;

&lt;p&gt;Before implementing autoscaling, ensure that your monitoring and metric systems are in place.&lt;/p&gt;
&lt;h3&gt;
  
  
  Required Tools: cAdvisor, Prometheus, and KEDA
&lt;/h3&gt;

&lt;p&gt;To make HPA and KEDA work efficiently, you’ll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;cAdvisor&lt;/strong&gt; – collects container-level CPU and memory metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus&lt;/strong&gt; – scrapes, stores, and visualizes time-series metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus Adapter or KEDA&lt;/strong&gt; – exposes those metrics to the HPA.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup is essential to achieve a reliable, responsive scaling mechanism that monitors pod and container utilization with fine-grained metric resolution (ideally 1 second or less).&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting Up Metrics for HPA
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Configuring cAdvisor for container-level metrics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;cAdvisor (Container Advisor)&lt;/strong&gt; is a running daemon that provides per-container resource usage data. It collects and exports information about all containers running on a host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key configuration tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid collecting all metrics to reduce resource consumption.&lt;/li&gt;
&lt;li&gt;Adjust collection intervals according to your scaling sensitivity.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--allow_dynamic_housekeeping=false&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--housekeeping_interval=1s&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--max_housekeeping_interval=2s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These flags ensure you get up-to-date metrics while keeping overhead low.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prometheus Configuration for Scraping cAdvisor Data
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Prometheus&lt;/strong&gt; collects metrics from cAdvisor and stores them for HPA or KEDA to consume. Below is a sample configuration to scrape and relabel container metrics efficiently:&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;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cadvisor&lt;/span&gt;
    &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1s&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cadvisor.cadvisor.svc.cluster.local:8080"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;metric_relabel_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;container_label_io_kubernetes_pod_namespace&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;namespace&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;container_label_io_kubernetes_pod_name&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pod&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;container_label_io_kubernetes_container_name&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;container_label_io_kubernetes_pod_node_name&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container_label_.*&lt;/span&gt;
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;labeldrop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Use a 1-second scrape interval for real-time scaling accuracy, but monitor your Prometheus load — frequent scrapes can impact cluster performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Prometheus Adapter to Expose Custom Metrics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Prometheus Adapter&lt;/strong&gt; acts as a bridge between Prometheus and the Kubernetes HPA. It translates Prometheus metrics into Kubernetes-readable custom metrics that HPA can act upon.&lt;/p&gt;

&lt;p&gt;Example configuration for CPU and memory metrics:&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;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;containerQuery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;sum by (&amp;lt;&amp;lt;.GroupBy&amp;gt;&amp;gt;) (&lt;/span&gt;
        &lt;span class="s"&gt;rate(container_cpu_usage_seconds_total{container!="",&amp;lt;&amp;lt;.LabelMatchers&amp;gt;&amp;gt;}[5s])&lt;/span&gt;
      &lt;span class="s"&gt;)&lt;/span&gt;
    &lt;span class="na"&gt;nodeQuery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;sum  by (&amp;lt;&amp;lt;.GroupBy&amp;gt;&amp;gt;) (&lt;/span&gt;
        &lt;span class="s"&gt;rate(node_cpu_seconds_total{mode!="idle",mode!="iowait",mode!="steal",&amp;lt;&amp;lt;.LabelMatchers&amp;gt;&amp;gt;}[3m])&lt;/span&gt;
      &lt;span class="s"&gt;)&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;overrides&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;namespace&lt;/span&gt;
        &lt;span class="na"&gt;pod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pod&lt;/span&gt;
    &lt;span class="na"&gt;containerLabel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container&lt;/span&gt;
  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;containerQuery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;sum by (&amp;lt;&amp;lt;.GroupBy&amp;gt;&amp;gt;) (&lt;/span&gt;
        &lt;span class="s"&gt;avg_over_time(container_memory_working_set_bytes{container!="",&amp;lt;&amp;lt;.LabelMatchers&amp;gt;&amp;gt;}[5s])&lt;/span&gt;
      &lt;span class="s"&gt;)&lt;/span&gt;
    &lt;span class="na"&gt;nodeQuery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;sum by (&amp;lt;&amp;lt;.GroupBy&amp;gt;&amp;gt;) (&lt;/span&gt;
        &lt;span class="s"&gt;avg_over_time(node_memory_MemTotal_bytes{&amp;lt;&amp;lt;.LabelMatchers&amp;gt;&amp;gt;}[3m])&lt;/span&gt;
        &lt;span class="s"&gt;-&lt;/span&gt;
        &lt;span class="s"&gt;avg_over_time(node_memory_MemAvailable_bytes{&amp;lt;&amp;lt;.LabelMatchers&amp;gt;&amp;gt;}[3m])&lt;/span&gt;
      &lt;span class="s"&gt;)&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;overrides&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;namespace&lt;/span&gt;
        &lt;span class="na"&gt;pod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pod&lt;/span&gt;
    &lt;span class="na"&gt;containerLabel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration exposes per-pod resource metrics that HPA can use to make scaling decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling Medusa with KEDA
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How KEDA Integrates with Kubernetes HPA&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KEDA (Kubernetes Event-Driven Autoscaler)&lt;/strong&gt; enhances Kubernetes autoscaling by allowing scaling based on external events - for example, message queue depth, API requests, or Prometheus metrics.&lt;/p&gt;

&lt;p&gt;KEDA works alongside HPA to provide &lt;strong&gt;fine-grained, event-driven scaling&lt;/strong&gt; for your Medusa backend.&lt;/p&gt;

&lt;p&gt;Below is a sample configuration for scaling Medusa using KEDA with Prometheus as the metric source:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keda.sh/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ScaledObject&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;medusa-backend&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;medusa-backend&lt;/span&gt;
  &lt;span class="na"&gt;pollingInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;cooldownPeriod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
  &lt;span class="na"&gt;minReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
      &lt;span class="na"&gt;metricType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AverageValue&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;serverAddress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;http://prometheus-server.default.svc.cluster.local:80&amp;gt;&lt;/span&gt;
        &lt;span class="na"&gt;metricName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;avg_cpu_usage&lt;/span&gt;
        &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.5"&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;sum by (pod) (rate(container_cpu_usage_seconds_total{pod=~"medusa-backend-.*", container!=""}[5s]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This YAML defines a &lt;strong&gt;ScaledObject&lt;/strong&gt; that dynamically adjusts Medusa’s replica count based on CPU usage metrics scraped by Prometheus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fine-Tuning Kubernetes HPA
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;kube-controller-manager Parameters for Faster Scaling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;kube-controller-manager&lt;/strong&gt; is a core Kubernetes component that controls how quickly HPA reacts to metric changes.&lt;/p&gt;

&lt;p&gt;By tuning its parameters, you can make scaling nearly instantaneous - for example, achieving a &lt;strong&gt;5-second response time&lt;/strong&gt; to CPU utilization spikes.&lt;/p&gt;

&lt;p&gt;Ensure you’ve optimized:&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="nt"&gt;--horizontal-pod-autoscaler-sync-period&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lowering this interval makes the autoscaler evaluate conditions more frequently, but note that some managed Kubernetes distributions may restrict access to these flags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visualizing Scaling with Grafana
&lt;/h3&gt;

&lt;p&gt;Grafana dashboards can help you track CPU utilization, pod counts, and scaling behavior in real time.&lt;br&gt;
Below is an example visualization showing how HPA scales pods in and out based on CPU load.&lt;/p&gt;

&lt;p&gt;This visualization also highlights the importance of minimizing &lt;strong&gt;container startup and readiness probe times&lt;/strong&gt;, which directly affect how quickly new replicas become active.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo4f56aejpifi28446isw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo4f56aejpifi28446isw.webp" alt="Grafana dashboards to track CPU utilization, pod counts, and scaling behavior in real time." width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices and Common Pitfalls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Optimize Startup Times and Readiness Probes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure Medusa starts and becomes ready as quickly as possible.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;readiness probes&lt;/strong&gt; to signal when pods can receive traffic.&lt;/li&gt;
&lt;li&gt;Long startup times can delay scaling and degrade performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Selecting the Right Scaling Metrics&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU utilization is common, but not always optimal.&lt;/li&gt;
&lt;li&gt;Consider custom business metrics (e.g., requests per second, queue depth).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dealing with HPA Sync Delays&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The default HPA sync period is &lt;strong&gt;15 seconds&lt;/strong&gt;, which might be too slow for bursty workloads.&lt;/li&gt;
&lt;li&gt;Decreasing it improves responsiveness but can increase API traffic and controller load.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Implementing &lt;strong&gt;HPA and KEDA&lt;/strong&gt; for your &lt;strong&gt;Medusa.js e-commerce platform&lt;/strong&gt; ensures efficient scaling, better performance under load, and optimal resource utilization.&lt;/p&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always configure readiness probes and optimize startup times.&lt;/li&gt;
&lt;li&gt;Choose scaling metrics carefully - CPU isn’t always the best indicator.&lt;/li&gt;
&lt;li&gt;Monitor your autoscaler’s responsiveness using Grafana and Prometheus.&lt;/li&gt;
&lt;li&gt;Test your setup under realistic load scenarios to validate scaling behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the right configuration, Kubernetes can make your Medusa deployment both &lt;strong&gt;resilient and self-scaling&lt;/strong&gt;, ensuring you’re always ready for traffic spikes.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ: Scaling Medusa in Kubernetes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: What is the best way to scale Medusa on Kubernetes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A1: Use Kubernetes HPA with metrics from Prometheus and cAdvisor, or event-driven scaling through KEDA for more flexibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: Does Medusa support autoscaling natively?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A2: Medusa itself doesn’t manage scaling, but it runs well in Kubernetes environments that use HPA or KEDA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Which metrics should I track for autoscaling?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A3: Start with CPU and memory usage; consider adding metrics like request rate, queue size, or API latency for advanced control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: How can I monitor Medusa’s scaling performance?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A4: Integrate Prometheus with Grafana dashboards to visualize pod utilization and replica changes in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: What’s the difference between HPA and KEDA?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A5: HPA relies on internal Kubernetes metrics, while KEDA extends it to external sources like Prometheus queries or event systems.&lt;/p&gt;

</description>
      <category>medusa</category>
      <category>kubernetes</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Missing files in your Packer built image? You might be skipping graceful shutdowns</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Thu, 23 Oct 2025 11:04:49 +0000</pubDate>
      <link>https://forem.com/u11d/missing-files-in-your-packer-built-image-you-might-be-skipping-graceful-shutdowns-4ccj</link>
      <guid>https://forem.com/u11d/missing-files-in-your-packer-built-image-you-might-be-skipping-graceful-shutdowns-4ccj</guid>
      <description>&lt;p&gt;Are your files and folders missing after you run the newly created image? This is a common issue when building images with &lt;a href="https://developer.hashicorp.com/packer" rel="noopener noreferrer"&gt;Packer&lt;/a&gt;. It is a widely used tool for creating machine images in a repeatable and automated way. When using the &lt;a href="https://developer.hashicorp.com/packer/integrations/hashicorp/qemu/latest/components/builder/qemu" rel="noopener noreferrer"&gt;&lt;code&gt;QEMU builder&lt;/code&gt;&lt;/a&gt; to generate local or CI-friendly images, it's common to upload files with the &lt;a href="https://developer.hashicorp.com/packer/docs/provisioners/file" rel="noopener noreferrer"&gt;&lt;code&gt;file provisioner&lt;/code&gt;&lt;/a&gt; and configure systems using &lt;a href="https://developer.hashicorp.com/packer/docs/provisioners/shell" rel="noopener noreferrer"&gt;&lt;code&gt;shell provisioner&lt;/code&gt;&lt;/a&gt;. One often overlooked but critical part of this workflow is how the virtual machine shuts down after provisioning. Without a proper shutdown, the resulting image can be unstable, incomplete, or even unbootable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let’s see why &lt;strong&gt;graceful shutdowns&lt;/strong&gt; matter.
&lt;/h3&gt;

&lt;p&gt;After provisioning completes, &lt;a href="https://developer.hashicorp.com/packer" rel="noopener noreferrer"&gt;Packer&lt;/a&gt; snapshots the virtual disk to produce the final image. If the guest operating system is not properly shut down before this snapshot, you risk file system corruption, lost configuration changes, services failing to start, or persistent files missing entirely - especially those written during the final steps of provisioning. These issues often go unnoticed until the image is deployed in staging or production environments, where the consequences are far more disruptive.&lt;/p&gt;

&lt;p&gt;By default, the &lt;code&gt;shutdown_command&lt;/code&gt; is set to an empty string (&lt;code&gt;""&lt;/code&gt;). This means &lt;a href="https://developer.hashicorp.com/packer" rel="noopener noreferrer"&gt;Packer&lt;/a&gt; does not attempt a clean shutdown unless you explicitly configure one. Instead, the virtual machine is forcefully terminated, which is equivalent to abruptly cutting power on a physical machine - a common cause of data loss and image inconsistency.&lt;/p&gt;

&lt;p&gt;To prevent this, you must define a &lt;code&gt;shutdown_command&lt;/code&gt; that instructs the OS to shut down safely after provisioning. This ensures that services have time to stop, file operations complete, and the system reaches a stable state before the image is captured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Correct usage of &lt;code&gt;shutdown_command&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A clean shutdown requires a properly formed command with the necessary privileges, and the exact command you use depends on the operating system inside the virtual machine. Most Linux distributions support the &lt;code&gt;shutdown&lt;/code&gt; command, but the syntax, required privileges, and available tools can vary slightly.&lt;/p&gt;

&lt;p&gt;Here’s a reliable example in HCL2 for a Debian/Ubuntu based image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="nx"&gt;shutdown_command&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"echo 'packer' | sudo --stdin shutdown --poweroff now"&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command assumes the use of a user with password-based &lt;code&gt;sudo&lt;/code&gt; access. It triggers a privileged system shutdown, allowing services to stop cleanly and file buffers to flush before power-off. The &lt;code&gt;--poweroff&lt;/code&gt; flag ensures the machine actually powers off, which is essential for clean termination when using virtual hardware emulation. Adjust the password to match your base image setup.&lt;/p&gt;

&lt;p&gt;If you're using a different base image - such as CentOS, RHEL, or Alpine - the shutdown binary might be located elsewhere, require different flags, or use an alternative like &lt;code&gt;poweroff&lt;/code&gt; or &lt;code&gt;halt&lt;/code&gt;. You may also need to configure &lt;code&gt;sudoers&lt;/code&gt; to avoid prompts or password issues during provisioning.&lt;/p&gt;

&lt;p&gt;Always test the shutdown command interactively inside a VM before inserting it into your template. It should work silently and reliably in the automated provisioning context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;To sum up, graceful shutdowns are a necessary safeguard when building machine images with the &lt;a href="https://developer.hashicorp.com/packer/integrations/hashicorp/qemu/latest/components/builder/qemu" rel="noopener noreferrer"&gt;&lt;code&gt;QEMU builder&lt;/code&gt;&lt;/a&gt; in &lt;a href="https://developer.hashicorp.com/packer" rel="noopener noreferrer"&gt;Packer&lt;/a&gt;. Skipping this step or relying on Packer’s default behavior (which can forcibly terminate the VM) introduces the risk of unstable and corrupted images. By using a proper &lt;code&gt;shutdown_command&lt;/code&gt;, you ensure the system shuts down in a controlled way, preserving the integrity of the file system and producing a clean, bootable image.&lt;/p&gt;

&lt;p&gt;This practice is simple to implement and greatly improves reliability in both development and production environments. If you're managing CI pipelines or building images for downstream automation, it's a foundational step that prevents subtle and costly failures later in the lifecycle.&lt;/p&gt;

</description>
      <category>packer</category>
      <category>qemu</category>
    </item>
    <item>
      <title>Headless CMS in E-Commerce: How to Integrate Medusa for Scalable, Content-Rich Online Stores</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Wed, 01 Oct 2025 08:03:00 +0000</pubDate>
      <link>https://forem.com/u11d/headless-cms-in-e-commerce-how-to-integrate-medusa-for-scalable-content-rich-online-stores-4kkd</link>
      <guid>https://forem.com/u11d/headless-cms-in-e-commerce-how-to-integrate-medusa-for-scalable-content-rich-online-stores-4kkd</guid>
      <description>&lt;p&gt;Modern e-commerce isn’t just about selling products; it’s about delivering dynamic, content-driven experiences across multiple channels. To achieve this, businesses lean on &lt;strong&gt;Content Management Systems (CMS)&lt;/strong&gt; for structured, scalable content workflows. With the emergence of headless commerce engines like Medusa.js and modern framework like Next.js, integrating a CMS has become more streamlined and customizable than ever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining CMS in the E-Commerce Stack
&lt;/h2&gt;

&lt;p&gt;A Content Management System (CMS) is a tool that lets you create, edit, and organize website content without touching code.&lt;/p&gt;

&lt;p&gt;In e-commerce terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It separates content (product descriptions, blog posts, banners) from your storefront (the part customers see and shop in).&lt;/li&gt;
&lt;li&gt;Editors and marketers can add or update pages in minutes without breaking anything.&lt;/li&gt;
&lt;li&gt;Developers don’t have to be “content gatekeepers” — the CMS is the safe workspace for all non-technical updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as Google Docs for your website content, but integrated into your online store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Medusa.js: Architecture and Integration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://medusajs.com" rel="noopener noreferrer"&gt;Medusa.js&lt;/a&gt; is an open-source, Node.js-based headless commerce engine. Unlike traditional monolithic platforms (e.g. Magento, Shopify), Medusa.js separates the commerce backend from the frontend, exposing commerce logic through APIs. This architecture supports integration with any frontend framework (e.g. Next.js) or CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Pair Medusa.js with a CMS?
&lt;/h3&gt;

&lt;p&gt;Medusa.js powers the transactional side of your storefront. A CMS adds that storytelling layer, and storefront (e.g. Next.js) is the perfect glue that pulls both content and commerce into a seamless experience.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blog posts&lt;/strong&gt; — CMS stores articles, Next.js fetches them alongside Medusa product data for a fully integrated shopping-and-reading experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Landing pages&lt;/strong&gt; — Launch seasonal campaigns in the CMS; Next.js dynamically renders them with real-time Medusa product data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich product descriptions&lt;/strong&gt; — Pull long-form text, videos, and extra images from the CMS while keeping inventory and pricing from Medusa.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO improvements&lt;/strong&gt; — Use Next.js server-side rendering (SSR) or static site generation (SSG) to ensure Google sees your content instantly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Decision Factors When Choosing a CMS
&lt;/h2&gt;

&lt;p&gt;When selecting a CMS to pair with Medusa, consider the following trade-offs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cloud (SaaS) vs. Self-Hosted&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud (SaaS)&lt;/strong&gt;: Ideal when your scale is small to medium or when you lack in-house DevOps expertise. Fast to set up, minimal maintenance, and predictable subscription costs. However, you trade off some control, and expenses can grow significantly as traffic, content volume, or user count increases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-Hosted&lt;/strong&gt;: Full control and deep customization, with the potential for lower raw hosting costs in the long term. But infrastructure setup, deployments, scaling, security, and ongoing maintenance require time and expertise — which translates directly into operational cost.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Friendliness vs. Ease of Use for Editors&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Some CMSs (like &lt;strong&gt;Payload&lt;/strong&gt;) are highly flexible and API-driven, offering developers full control but requiring training for non-technical editors.&lt;/li&gt;
&lt;li&gt;Others (like &lt;strong&gt;Strapi&lt;/strong&gt;) strike a balance, with strong developer features and a user-friendly admin panel.&lt;/li&gt;
&lt;li&gt;Platforms like &lt;strong&gt;Sanity&lt;/strong&gt; and &lt;strong&gt;Contentful&lt;/strong&gt; lean heavily toward editor experience, prioritizing usability and real-time collaboration.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community and Plugins&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;A strong community speeds up problem-solving and provides a library of ready-to-use plugins.&lt;/li&gt;
&lt;li&gt;For example, Strapi’s plugin ecosystem includes tools for image optimization, role-based permissions, analytics, and more.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;CMS Selection Matrix for Medusa.js Integration&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CMS&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Key Features&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Strapi&lt;/td&gt;
&lt;td&gt;Open-source (self-hosted or cloud)&lt;/td&gt;
&lt;td&gt;Customizable, large OSS community, plugin ecosystem&lt;/td&gt;
&lt;td&gt;Teams wanting balance between dev control and editor usability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload&lt;/td&gt;
&lt;td&gt;Open-source (self-hosted or cloud)&lt;/td&gt;
&lt;td&gt;Developer-centric, clean editor, powerful APIs&lt;/td&gt;
&lt;td&gt;Dev-heavy teams building tailored workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sanity&lt;/td&gt;
&lt;td&gt;Headless SaaS&lt;/td&gt;
&lt;td&gt;Real-time collaboration, content portability&lt;/td&gt;
&lt;td&gt;Content-heavy teams with remote collaboration needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contentful&lt;/td&gt;
&lt;td&gt;Headless SaaS&lt;/td&gt;
&lt;td&gt;Enterprise-ready, robust APIs, scalable&lt;/td&gt;
&lt;td&gt;Large organizations needing stability and global delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Example Implementation: Medusa + Strapi + Next.js
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Medusa.js&lt;/strong&gt;: Manages catalog, checkout, orders, and authentication workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strapi&lt;/strong&gt;: Controls homepage banners, blog content, landing pages, and SEO metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt;: Consumes APIs from both Medusa and Strapi to render the frontend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This decoupled structure allows content managers and developers to work in parallel, ensuring rapid deployment and updates across both commerce and content layers.&lt;/p&gt;

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

&lt;p&gt;Combining a headless CMS with Medusa.js and modern storefront framework enables highly customizable, scalable, and performant e-commerce platforms. This architecture decouples content and commerce, allowing independent optimization and rapid iteration. As the industry moves toward open, API-driven systems, Medusa.js and compatible CMS platforms provide a robust foundation for next-generation digital commerce.&lt;/p&gt;

</description>
      <category>medusa</category>
      <category>strapi</category>
      <category>nextjs</category>
      <category>payload</category>
    </item>
    <item>
      <title>Kubernetes logs unavailable behind a proxy: Diagnosing API server communication issues</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Wed, 06 Aug 2025 13:20:14 +0000</pubDate>
      <link>https://forem.com/u11d/kubernetes-logs-unavailable-behind-a-proxy-diagnosing-api-server-communication-issues-2af5</link>
      <guid>https://forem.com/u11d/kubernetes-logs-unavailable-behind-a-proxy-diagnosing-api-server-communication-issues-2af5</guid>
      <description>&lt;p&gt;In modern infrastructure setups, especially on-premises environments, the presence of outbound proxies is increasingly common. These proxies often enforce organizational access policies and provide observability into network traffic. While container orchestration platforms like Kubernetes can generally operate well under such constraints, some subtle and easily overlooked configuration issues can lead to unexpected behavior.&lt;/p&gt;

&lt;p&gt;In this article, I’ll walk through a real-world issue we encountered where Kubernetes pod logs were not retrievable via the API, despite the cluster appearing healthy otherwise. We'll also dive deeper into how proxy behavior interacts with Kubernetes components.&lt;/p&gt;

&lt;h3&gt;
  
  
  The symptom: cluster looks healthy, but &lt;code&gt;kubectl logs&lt;/code&gt; fails
&lt;/h3&gt;

&lt;p&gt;We were running a Kubernetes cluster on bare metal within a datacenter. The environment was placed behind a corporate HTTP proxy.&lt;/p&gt;

&lt;p&gt;On the surface, everything appeared operational:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kubectl get pods&lt;/code&gt;, &lt;code&gt;kubectl get svc&lt;/code&gt;, and other API queries worked fine,&lt;/li&gt;
&lt;li&gt;etcd connectivity was intact,&lt;/li&gt;
&lt;li&gt;Node and pod statuses reported healthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, when attempting to retrieve logs from running pods using &lt;code&gt;kubectl logs &amp;lt;pod&amp;gt;&lt;/code&gt;, the command failed with a timeout or a generic connection error. This was observed across all pods and namespaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  Investigating the root cause
&lt;/h3&gt;

&lt;p&gt;Kubernetes retrieves pod logs through the kubelet on the node where the pod is running. The kube-apiserver acts as a reverse proxy for these requests and must establish a direct connection to the kubelet endpoint (typically via HTTPS on port 10250). This is where the problem manifested.&lt;/p&gt;

&lt;p&gt;We first validated that logs were, in fact, being generated. By SSH-ing into the node where the pod was scheduled and using &lt;code&gt;crictl logs &amp;lt;container_id&amp;gt;&lt;/code&gt;, we could view logs without issue. This pointed to a problem not with the container runtime or logging configuration, but with how the kube-apiserver was trying to reach kubelet.&lt;/p&gt;

&lt;p&gt;Further inspection revealed that the kube-apiserver container was not bypassing the proxy for internal node-to-node communication. This wasn't a failure of the proxy settings - it was because the &lt;code&gt;NO_PROXY&lt;/code&gt; environment variable had never been set in the first place. In environments operating behind a proxy, this omission can critically impair the cluster's internal operations. Without an explicit &lt;code&gt;NO_PROXY&lt;/code&gt; configuration, even local traffic destined for internal services like the kubelet may be incorrectly routed through the proxy, leading to timeouts and unpredictable failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  How HTTP proxies work in Kubernetes environments
&lt;/h3&gt;

&lt;p&gt;To understand the root of the problem, it's important to grasp how HTTP proxies interact with Kubernetes networking.&lt;/p&gt;

&lt;p&gt;When you set the environment variables &lt;code&gt;HTTP_PROXY&lt;/code&gt; or &lt;code&gt;HTTPS_PROXY&lt;/code&gt;, applications on the host — including kube-apiserver, container runtime clients, and even kubelet — will route outbound traffic through the proxy server, unless explicitly told not to via &lt;code&gt;NO_PROXY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a Kubernetes cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most control plane components communicate over private IPs or node-local addresses,&lt;/li&gt;
&lt;li&gt;The kube-apiserver connects to the kubelet to fetch logs, execute commands, or forward ports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the API server tries to connect to the kubelet via a private IP (e.g., 10.0.0.12) and this IP is not included in the &lt;code&gt;NO_PROXY&lt;/code&gt;list, the request is sent to the proxy. Since proxies typically can't route to internal IPs or ports like 10250 (kubelet), the request fails silently or times out.&lt;/p&gt;

&lt;p&gt;This is further complicated on bare metal because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud provider integrations (e.g., in EKS, GKE) often auto-configure these exemptions,&lt;/li&gt;
&lt;li&gt;Static pods don’t inherit host environment variables unless explicitly defined in their manifests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The fix: update static pod environment variables
&lt;/h3&gt;

&lt;p&gt;Because kube-apiserver was running as a static pod, its environment variables had to be defined explicitly in the corresponding manifest file, typically located at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/etc/kubernetes/manifests/kube-apiserver.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We modified this file to include proper proxy settings, specifically ensuring the &lt;code&gt;NO_PROXY&lt;/code&gt; variable covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All node IP addresses&lt;/li&gt;
&lt;li&gt;The cluster CIDR&lt;/li&gt;
&lt;li&gt;The service CIDR&lt;/li&gt;
&lt;li&gt;Loopback addresses&lt;/li&gt;
&lt;li&gt;Hostnames used by the control plane&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a simplified version of the environment variable section added to the static pod spec:&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;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTP_PROXY&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://proxy.example.com:3128"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTPS_PROXY&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://proxy.example.com:3128"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NO_PROXY&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1,localhost,10.0.0.0/8,192.168.0.0/16,172.16.0.0/12,.cluster.local"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saving the file, the kubelet automatically reloaded the manifest and restarted the kube-apiserver. Once the API server came back online, &lt;code&gt;kubectl logs&lt;/code&gt; began returning expected output from all pods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaways and best practices
&lt;/h3&gt;

&lt;p&gt;This experience reinforced the importance of configuring &lt;code&gt;NO_PROXY&lt;/code&gt; correctly when operating in a proxied environment, particularly on bare metal where defaults that “just work” in cloud environments may not apply.&lt;/p&gt;

&lt;p&gt;Key points to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The API server needs direct access to kubelets to retrieve logs, exec into pods, and perform port-forwarding&lt;/li&gt;
&lt;li&gt;Failing to set &lt;code&gt;NO_PROXY&lt;/code&gt; for internal traffic can cause silent and hard-to-debug failures&lt;/li&gt;
&lt;li&gt;Static pods require environment variables to be set in the pod manifest, not in the host shell environment&lt;/li&gt;
&lt;li&gt;Use tools like &lt;code&gt;crictl&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, or &lt;code&gt;tcpdump&lt;/code&gt; on the control plane node to confirm where traffic is being routed&lt;/li&gt;
&lt;li&gt;Explicitly list IP ranges and DNS suffixes used within the cluster in &lt;code&gt;NO_PROXY&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>Layouts in Next.js: How to Structure Your E-commerce Platform the Right Way</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Mon, 04 Aug 2025 12:58:03 +0000</pubDate>
      <link>https://forem.com/u11d/layouts-in-nextjs-how-to-structure-your-e-commerce-platform-the-right-way-3g6n</link>
      <guid>https://forem.com/u11d/layouts-in-nextjs-how-to-structure-your-e-commerce-platform-the-right-way-3g6n</guid>
      <description>&lt;p&gt;The &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; 15 application router provides first-class support for layouts, making shared UI patterns seamless, efficient and composable. Here's a deeper dive into how &lt;a href="https://nextjs.org/docs/app/getting-started/layouts-and-pages" rel="noopener noreferrer"&gt;layouts&lt;/a&gt; work, their benefits and best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Layouts?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Layouts are React components that wrap pages or sub-layouts in &lt;code&gt;app/&lt;/code&gt;, rendering &lt;code&gt;children&lt;/code&gt; properties for &lt;a href="https://nextjs.org/docs/app/getting-started/layouts-and-pages#nesting-layouts" rel="noopener noreferrer"&gt;nested content&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The main layout (in &lt;code&gt;app/layout.tsx&lt;/code&gt;) is mandatory - it must contain &lt;code&gt;&amp;lt;html&amp;gt;...&amp;lt;body&amp;gt;&lt;/code&gt; tags and applies globally.&lt;/li&gt;
&lt;li&gt;Additional layouts in subdirectories (e.g., &lt;code&gt;app/categories/layout.tsx&lt;/code&gt;) only include routes in that segment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Folder-based hierarchy and nesting&lt;/p&gt;

&lt;p&gt;File system routing dictates layout behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;app/
  layout.tsx          ← root layout &lt;span class="o"&gt;(&lt;/span&gt;all routes&lt;span class="o"&gt;)&lt;/span&gt;
  page.tsx            ← homepage
  categories/
    layout.tsx        ← wraps categories routes
    page.tsx          ← /categories
    &lt;span class="o"&gt;[&lt;/span&gt;slug]/
      page.tsx        ← /categories/[slug]
  profile/
    layout.tsx        ← wraps client profile route
    page.tsx          ← /client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here root layout wraps every page, profile layout wraps only profile routes - nesting children accordingly. Wrapping a folder name in square brackets (e.g. &lt;code&gt;[slug]&lt;/code&gt;) creates a &lt;a href="https://nextjs.org/docs/app/api-reference/file-conventions/dynamic-routes" rel="noopener noreferrer"&gt;dynamic route segment&lt;/a&gt; which is used to generate multiple pages from data. (e.g. specific category pages, dedicated product pages, etc.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of using Layouts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Preservation of state&lt;/strong&gt;&lt;br&gt;
Since layouts persist during navigation, the state of shared components (e.g. &lt;code&gt;sidebars&lt;/code&gt;) is not lost, reducing reassembly and improving UX. This makes an ideal place to initialize global contexts. Especially useful when some of the values come from cookies or headers - helping to eliminate prop drilling and improving the maintainability of your codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Performance optimization&lt;/strong&gt;&lt;br&gt;
Layouts are server-side components by default and are cached. Client-side navigation keeps layouts live, minimizing re-rendering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Modular UI Structure&lt;/strong&gt;&lt;br&gt;
You can define different layouts for different sections of the application - e.g., &lt;code&gt;category area&lt;/code&gt; vs. &lt;code&gt;client profile&lt;/code&gt; - by nesting or using route groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Clean code organization&lt;/strong&gt;&lt;br&gt;
Layouts enforce structure and collocated logic - styling, metadata, additional providers - all in route-specific files.&lt;/p&gt;
&lt;h2&gt;
  
  
  First steps: Basic concepts
&lt;/h2&gt;

&lt;p&gt;Defining a root layout:&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;// app/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&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;html&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&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;children&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;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Footer&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/html&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;This defines a globally shared UI and is required.&lt;/p&gt;

&lt;p&gt;Adding nested layouts&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;// app/categories/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CategoriesLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&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;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SideNav&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&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;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex-grow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&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;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&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;Wraps &lt;code&gt;categories/&lt;/code&gt; routes, stacking inside the root layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Main vs. nested: Only main layouts contain &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tags. Subordinate layouts wrap inside them without duplicating these tags.&lt;/li&gt;
&lt;li&gt;Clean structure: Store &lt;code&gt;layout.tsx&lt;/code&gt;, &lt;code&gt;page.tsx&lt;/code&gt; and relevant UI components together by route segment.&lt;/li&gt;
&lt;li&gt;Selective nesting: Use nested layouts only where necessary to avoid unnecessary UI on pages.&lt;/li&gt;
&lt;li&gt;Use of server components: Default layouts are server-side - ideal for retrieving data via parameters or shared UI logic.&lt;/li&gt;
&lt;li&gt;Route groups: Create clear layout boundaries (e.g., &lt;code&gt;categories/&lt;/code&gt; vs. &lt;code&gt;profile/&lt;/code&gt;) to isolate sections - allowing for multiple layouts at the root level if needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Summary&lt;/h2&gt;

&lt;p&gt;Layouts empower you to:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Function&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Shared UI&lt;/td&gt;
      &lt;td&gt;Headers, footers, navigations applied globally or segmented&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Nested UIs&lt;/td&gt;
      &lt;td&gt;Each route folder can define its own wrap layout&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Smooth state&lt;/td&gt;
      &lt;td&gt;Maintain React state in nav without full reloading&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Organized code&lt;/td&gt;
      &lt;td&gt;UI and logic co-located per route for ease of maintenance&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Define a global shell in &lt;code&gt;app/layout.tsx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add segment-specific layouts if necessary.&lt;/li&gt;
&lt;li&gt;Organize code by route directory for modularity.&lt;/li&gt;
&lt;li&gt;Use route groups if you separate contexts with different user interfaces.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These patterns make your Next.js application more maintainable, efficient and user-friendly.&lt;/p&gt;

&lt;p&gt;Happy building!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Manage user cookie consent with Google Tag Manager: a step-by-step guide</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Mon, 14 Jul 2025 07:00:00 +0000</pubDate>
      <link>https://forem.com/u11d/manage-user-cookie-consent-with-google-tag-manager-a-step-by-step-guide-313e</link>
      <guid>https://forem.com/u11d/manage-user-cookie-consent-with-google-tag-manager-a-step-by-step-guide-313e</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Google Tag Manager (GTM) is a highly useful tool that can assist in managing website's tags and pixels with ease. However, with increased privacy regulations such as the GDPR and equivalents, it is essential to ensure that your website's cookie policy is fully compliant. GTM introduced a built-in cookie consent feature in 2018, which enables website owners to manage User consent for cookies and tracking technologies, but it's not enabled by default.&lt;/p&gt;

&lt;p&gt;In this blog post, I will walk through the cookie consent feature and discuss the advantages of using Google Tag Manager, the significance of configuring it properly to comply with actual cookie policy requirements, and provide a comprehensive guide on how to do it correctly. Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages of Google Tag Manager
&lt;/h2&gt;

&lt;p&gt;Out of all the benefits offered by Google Tag Manager, I have identified four key advantages that I consider to be the most significant. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Google Tag Manager provides the advantage of easily managing tags and pixels without relying on the development team to attach additional scripts to a product. This saves time and resources, allowing marketers and non-technical Users to take control of tag management after a proper configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another advantage is the ability to customize the cookie banner without making changes to a product. With GTM, you can easily modify the cookie banner to comply with privacy regulations and reflect your branding as well as providing a seamless User experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GTM offers the flexibility to switch between previous versions and publish any of them at any time. This version control feature allows you to make changes and experiment with different configurations, ensuring smooth transitions and reducing the risk of errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The ability to export and import the container configuration is yet another benefit. This functionality enables you to use the same configuration across different company websites, streamlining the deployment process and maintaining consistency in your tracking and analytics setup.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to configure Google Tag Manager properly?
&lt;/h2&gt;

&lt;p&gt;By default, GTM uses the "Page View" trigger to fire tags when a page loads. This means that any tags that are added to a Google Tag Manager container will be automatically triggered when a User lands on the page. Unfortunately nowadays it cannot work like that. Because of the latest privacy regulations the User should be able to decide whether he want to give its consent to manage the cookies or not.&lt;/p&gt;

&lt;p&gt;To help ensure compliance with cookie policy regulations, website owners should follow these key steps to configure their Google Tag Manager scripts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a cookie consent banner: Before any cookies can be set, Users must give their consent. A cookie consent banner should be added to your website that clearly explains what cookies are being used and why. The banner should also give Users the option to accept or reject cookies.&lt;/li&gt;
&lt;li&gt;Enable Google Tag Manager's built-in consent features: Google Tag Manager has built-in consent features that can be enabled to ensure that tags are only fired once Users have given their consent. This can be done by setting up consent triggers and conditions within Google Tag Manager.&lt;/li&gt;
&lt;li&gt;Configure tags to honor cookie consent: Once consent has been given, you'll need to configure your tags to honor the User's choice. This can be done by setting up tag firing triggers and conditions within Google Tag Manager that only fire when the User has given their consent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By following these steps, you can configure your Google Tag Manager scripts to meet cookie policy regulations and ensure that you are respecting Users' privacy while still delivering a great User Experience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✏️ &lt;strong&gt;Note:&lt;/strong&gt; Remember to regularly review and update your cookie policy. Regulations may change over time, so it's important to regularly review and update your cookie policy to ensure that it remains compliant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Adding a Cookie Consent Banner
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; If you want to use version 3, we recommend checking &lt;a href="https://u11d.com/blog/manage-user-cookie-consent-with-google-tag-manager-adapting-to-cookie-consent-v3/" rel="noopener noreferrer"&gt;our article&lt;/a&gt; describing the changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To implement the cookie banner, I have selected &lt;a href="https://github.com/orestbida/cookieconsent/tree/v2.9" rel="noopener noreferrer"&gt;Cookie Consent v2.9&lt;/a&gt;, one of the most popular open-source projects on GitHub. It allows us to customize its design, content, and even add custom logic after the User accepts the selected options.&lt;/p&gt;

&lt;p&gt;To add a custom banner to Google Tag Manager, follow these steps:&lt;/p&gt;

&lt;p&gt;Open &lt;a href="https://tagmanager.google.com/" rel="noopener noreferrer"&gt;Google Tag Manager&lt;/a&gt; and select your preferred container. If you don't have a configured account in GTM or you don't have an open container, you can click on this &lt;a href="https://support.google.com/tagmanager/answer/12974036?hl=en" rel="noopener noreferrer"&gt;link&lt;/a&gt; to learn how to set it up.&lt;/p&gt;

&lt;p&gt;Select the &lt;strong&gt;Tags&lt;/strong&gt; option from the left-side menu and click on the &lt;strong&gt;New&lt;/strong&gt; button.&lt;/p&gt;

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

&lt;p&gt;Then, click on &lt;strong&gt;Tag Configuration&lt;/strong&gt; and choose &lt;strong&gt;Custom HTML&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;The code below contains the default configuration of the Cookie Consent. You can customize various aspects, such as the placement of the banner, button settings (including roles and labels), titles, and section descriptions. If you wish to personalize the banner's design, you can visit the &lt;a href="https://orestbida.com/demo-projects/cookieconsent/" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;. In the Playground, you'll find a section explaining "How to use custom themes" along with a few example themes provided above it.&lt;/p&gt;

&lt;p&gt;Now, paste the provided example code for the cookie banner into the &lt;strong&gt;HTML&lt;/strong&gt; field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="stylesheet" type="text/css"
  href="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v2.9.1/dist/cookieconsent.css" /&amp;gt;
&amp;lt;script src="https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v2.9.1/dist/cookieconsent.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
  var cc = initCookieConsent();

  cc.run({
    current_lang: 'en',
    autoclear_cookies: true,

    onFirstAction: function (user_preferences, cookie) {
      // callback triggered only once on the first accept/reject action
    },

    gui_options: {
      consent_modal: {
        layout: 'cloud',               // box/cloud/bar
        position: 'bottom left',       // bottom/middle/top + left/right/center
        transition: 'slide',           // zoom/slide
        swap_buttons: false            // enable to invert buttons
      },
      settings_modal: {
        layout: 'box',                 // box/bar
        // position: 'left',           // left/right
        transition: 'slide'            // zoom/slide
      }
    },

    languages: {
      'en': {
        consent_modal: {
          title: 'We use cookies!',
          description: 'Hi, this website uses essential cookies to ensure its proper operation and tracking cookies to understand how you interact with it. The latter will be set only after consent. &amp;lt;button type="button" data-cc="c-settings" class="cc-link"&amp;gt;Let me choose&amp;lt;/button&amp;gt;',
          primary_btn: {
            text: 'Accept all',
            role: 'accept_all'              // 'accept_selected' or 'accept_all'
          },
          secondary_btn: {
            text: 'Reject all',
            role: 'accept_necessary'        // 'settings' or 'accept_necessary'
          }
        },
        settings_modal: {
          title: 'Cookie preferences',
          save_settings_btn: 'Save settings',
          accept_all_btn: 'Accept all',
          reject_all_btn: 'Reject all',
          close_btn_label: 'Close',
          // cookie_table_caption: 'Cookie list',
          cookie_table_headers: [
            { col1: 'Name' },
            { col2: 'Domain' },
            { col3: 'Expiration' },
            { col4: 'Description' }
          ],
          blocks: [
            {
              title: 'Cookie usage 📢',
              description: 'I use cookies to ensure the basic functionalities of the website and to enhance your online experience. You can choose for each category to opt-in/out whenever you want. For more details relative to cookies and other sensitive data, please read the full &amp;lt;a href="#" class="cc-link"&amp;gt;privacy policy&amp;lt;/a&amp;gt;.'
            }, {
              title: 'Strictly necessary cookies',
              description: 'These cookies are essential for the proper functioning of my website. Without these cookies, the website would not work properly',
              toggle: {
                value: 'necessary',
                enabled: true,
                readonly: true          // cookie categories with readonly=true are all treated as "necessary cookies"
              }
            }, {
              title: 'Performance and Analytics cookies',
              description: 'These cookies allow the website to remember the choices you have made in the past',
              toggle: {
                value: 'analytics',     // your cookie category
                enabled: false,
                readonly: false
              },
              cookie_table: [             // list of all expected cookies
                {
                  col1: '^_ga',       // match all cookies starting with "_ga"
                  col2: 'google.com',
                  col3: '2 years',
                  col4: 'description ...',
                  is_regex: true
                },
                {
                  col1: '_gid',
                  col2: 'google.com',
                  col3: '1 day',
                  col4: 'description ...',
                }
              ]
            }, {
              title: 'Advertisement and Targeting cookies',
              description: 'These cookies collect information about how you use the website, which pages you visited and which links you clicked on. All of the data is anonymized and cannot be used to identify you',
              toggle: {
                value: 'targeting',
                enabled: false,
                readonly: false
              }
            }, {
              title: 'More information',
              description: 'For any queries in relation to our policy on cookies and your choices, please &amp;lt;a class="cc-link" href="#yourcontactpage"&amp;gt;contact us&amp;lt;/a&amp;gt;.',
            }
          ]
        }
      }
    }
  });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Scroll to the bottom and select the trigger labeled &lt;strong&gt;Initialization - All Pages&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Rename the "Untitled Tag" to &lt;code&gt;Custom Cookie Banner&lt;/code&gt; and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Finally, your tags list should look similar to this:&lt;/p&gt;

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

&lt;p&gt;To verify that everything is working correctly so far, you need to add the Google Tag Manager script to your webpage. Click on &lt;strong&gt;GTM-[your_id]&lt;/strong&gt; next to the "Preview" and "Submit" buttons. A modal window will appear with instructions on how to proceed.&lt;/p&gt;

&lt;p&gt;After adding the script, click on &lt;strong&gt;Preview&lt;/strong&gt; and provide a link to your website where you have added the Google Tag Manager script. For example, I have added it to &lt;code&gt;index.html&lt;/code&gt; on my local server, so I will use the &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt; URL. Then, click &lt;strong&gt;Connect&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the Tag Manager Assistant is successfully connected, you will see the following message:&lt;/p&gt;

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

&lt;p&gt;Additionally, a new browser window will open with the provided URL. If you have followed the same steps, you shall see the Cookie Banner at the bottom of the browser window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx2697wfwuja1wt4oh12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx2697wfwuja1wt4oh12.png" alt="Cookie Consent Banner in the User's Browser" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The selected cookie banner provides the User with all the necessary options. Users can accept all cookies, reject all cookies, or choose from the available choices.&lt;/p&gt;

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

&lt;p&gt;Once the User makes a choice, the cookie consent will add a technical cookie (default name: cc_cookie) to the User's browser.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; The &lt;code&gt;__TAG_ASSISTANT&lt;/code&gt; technical cookie will exclusively appear for individuals using the Tag Assistant tool for debugging purposes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the complete content of the cc_cookie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"categories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"necessary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Always&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;present&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"analytics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Appears&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;accepts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cookies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;selects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;analytics&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"targeting"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Appears&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;accepts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cookies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;selects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;targeting&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"necessary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"analytics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"targeting"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rfc_cookie"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consent_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-04-25T14:09:48.764Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consent_uuid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e9eb8459-7a72-4cc7-8251-31f581c78727"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_consent_update"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-04-25T14:09:48.764Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GTM Variable Configuration
&lt;/h2&gt;

&lt;p&gt;In order to capture the User's technical cookie containing consent information, we need to add variables to Google Tag Manager.&lt;/p&gt;

&lt;p&gt;Open the &lt;strong&gt;Variables&lt;/strong&gt; section from the left-side menu and click on &lt;strong&gt;New&lt;/strong&gt; in the "User-Defined Variables" section.&lt;/p&gt;

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

&lt;p&gt;Click on &lt;strong&gt;Variable Configuration&lt;/strong&gt; and select &lt;strong&gt;1st Party Cookie&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Enter your cookie name (default: &lt;code&gt;cc_cookie&lt;/code&gt;) in the cookie name field. Then, rename the variable from "Untitled Variable" to &lt;code&gt;CC Cookie&lt;/code&gt; and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;To verify if Google Tag Manager is capturing the cookie and its values, you can check the preview mode once again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; Remember to close and reopen the Tag Assistant tab every time you make changes to the configuration. This is necessary because the previously opened tab won't sync with the latest changes.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; If you don't see the cookie banner in the newly opened tab, go to the developer tools in your browser, remove cc_cookie, and refresh the page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Initially, when a new User opens your page, the status of the CC Cookie (GTM Variable) will be set to &lt;code&gt;undefined&lt;/code&gt;. You can check this in Tag Assistant.&lt;/p&gt;

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

&lt;p&gt;After clicking the &lt;strong&gt;Accept All&lt;/strong&gt; button in the cookie banner, the User's browser will receive a technical cookie that complies with GDPR. When the page is refreshed, Tag Assistant will show us the values of the accepted consents.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Note that every time the page is refreshed, Tag Assistant will display new entries in the left menu. Remember to select the new entry if you want to check for any changes.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;After adding User's technical cookie handling to GTM Variables, let's add a few more variables to help us manage User's consent related to specific categories.&lt;/p&gt;

&lt;p&gt;Open the &lt;strong&gt;Variables&lt;/strong&gt; menu once again and click &lt;strong&gt;New&lt;/strong&gt;. This time, select the &lt;strong&gt;Custom JavaScript&lt;/strong&gt; option.&lt;/p&gt;

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

&lt;p&gt;The code snippet below checks the value of the CC Cookie. If the cookie is not present, it returns 'denied'. If the cookie is present and the 'analytics' category is included in the cookie, it returns 'granted'. Otherwise, it returns 'denied'.&lt;/p&gt;

&lt;p&gt;Paste the provided code below into the editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;CC&lt;/span&gt; &lt;span class="nx"&gt;Cookie&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;cookie&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;denied&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookie&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;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;categories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analytics&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="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;granted&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;denied&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rename this new variable to &lt;code&gt;Consent - Analytics&lt;/code&gt; and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;This variable will essentially return a value of &lt;code&gt;granted&lt;/code&gt; if the User accepts all or analytical cookies, and &lt;code&gt;denied&lt;/code&gt; otherwise.&lt;/p&gt;

&lt;p&gt;Let's add one more variable. Once again, copy the variable code, but this time change the conditional statement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;from &lt;code&gt;json["categories"].includes("analytics")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;to &lt;code&gt;json["categories"].includes("targeting")&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rename the variable to &lt;code&gt;Consent - Targeting&lt;/code&gt; and click &lt;strong&gt;Save&lt;/strong&gt;. After completing this operation, the list of variables should look like this:&lt;/p&gt;

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

&lt;p&gt;Let's check if everything is working correctly. Click on the &lt;strong&gt;Preview&lt;/strong&gt; button and open Tag Assistant.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; Remember to close and reopen the Tag Assistant tab every time you make changes to the configuration. This is necessary because the previously opened tab won't sync with the latest changes.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; If you don't see the cookie banner in the newly opened tab, go to the developer tools in your browser, remove cc_cookie, and refresh the page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When a new User visits our site, the CC Cookie variable will return &lt;code&gt;undefined&lt;/code&gt;, and both the &lt;strong&gt;Consent - Analytics&lt;/strong&gt; and &lt;strong&gt;Consent - Targeting&lt;/strong&gt; variables will return &lt;code&gt;denied&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Now, let's accept only Performance and Analytical cookies in our consent banner and click &lt;strong&gt;Save Settings&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;After saving the settings, refresh the page, and you'll see new entries in Tag Assistant.&lt;/p&gt;

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

&lt;p&gt;Now you can see that Tag Manager shows the value of CC Cookie instead of &lt;code&gt;undefined&lt;/code&gt;, and the value of &lt;strong&gt;Consent - Analytics&lt;/strong&gt; has changed from &lt;code&gt;denied&lt;/code&gt; to &lt;code&gt;granted&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Triggers
&lt;/h2&gt;

&lt;p&gt;Next, let's set up triggers that will notify GTM about consent updates. Additionally, we'll configure another trigger (to be used later) to activate consent blocked tags specifically for users who have accepted certain consents.&lt;/p&gt;

&lt;p&gt;Open the &lt;strong&gt;Triggers&lt;/strong&gt; menu from the left-side menu and click &lt;strong&gt;New&lt;/strong&gt;. Choose a trigger type of &lt;strong&gt;Custom Event&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;In the event name field, enter &lt;code&gt;client-consent-update&lt;/code&gt; and select the &lt;strong&gt;All custom events&lt;/strong&gt; radio button option.&lt;/p&gt;

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

&lt;p&gt;Rename it to &lt;code&gt;Client Consent Update&lt;/code&gt; and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We will add this trigger to our consent banner, so that every time the User clicks on any button, this trigger will be fired.&lt;/p&gt;

&lt;p&gt;Now, let's add one more trigger. This one will be needed after GTM updates the consent data on its side.&lt;/p&gt;

&lt;p&gt;Once again, click the &lt;strong&gt;New&lt;/strong&gt; button and select &lt;strong&gt;Custom Event&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Fill the event name field with the value of &lt;code&gt;gtm-consent-updated&lt;/code&gt; and select the &lt;strong&gt;All custom events&lt;/strong&gt; radio button option.&lt;/p&gt;

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

&lt;p&gt;Rename the trigger to &lt;code&gt;GTM Consent Updated&lt;/code&gt; and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Finally, let's add the code that will trigger the "Client Consent Update" to the previously added Custom Cookie Banner.&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;Tags&lt;/strong&gt; and click on &lt;strong&gt;Custom Cookie Banner&lt;/strong&gt;. Modify the &lt;code&gt;onFirstAction&lt;/code&gt; function:&lt;/p&gt;

&lt;p&gt;From:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;onFirstAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user_preferences&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// callback triggered only once on the first accept/reject action&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;onFirstAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&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;dataLayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event&lt;/span&gt;&lt;span class="dl"&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;client-consent-update&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Save the latest changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update GTM Consents
&lt;/h2&gt;

&lt;p&gt;Now, I will show you how to enable GTM Consent Overview mode and how to automatically update User consent.&lt;/p&gt;

&lt;p&gt;To enable GTM Consent Overview mode, follow these steps:&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;Admin&lt;/strong&gt; tab below the Tag Manager logo, and then open &lt;strong&gt;Container Settings&lt;/strong&gt; from the menu on the right.&lt;/p&gt;

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

&lt;p&gt;Select the &lt;strong&gt;Enable consent overview&lt;/strong&gt; checkbox and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;With this option enabled, you'll be able to display an overview of all tags and their associated consents.&lt;/p&gt;

&lt;p&gt;I'll show you how it works a little later. Now, let's add a tag that will automatically update GTM Consent settings after the User's click without the need to refresh the page.&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;Workspace&lt;/strong&gt; tab below the Tag Manager logo, open the &lt;strong&gt;Tags&lt;/strong&gt; menu on the left, and click on the &lt;strong&gt;New&lt;/strong&gt; button inside the "Tags" table.&lt;/p&gt;

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

&lt;p&gt;Click on &lt;strong&gt;Tag Configuration&lt;/strong&gt; and select &lt;strong&gt;Discover more tag types in the Community Template Gallery&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Next, click on the search icon and type &lt;code&gt;Consent Mode&lt;/code&gt; Select &lt;strong&gt;Consent Mode (Google tags)&lt;/strong&gt; by gtm-templates-simo-ahava. If you'd like to check out the code for this plugin, it is available in &lt;a href="https://github.com/gtm-templates-simo-ahava/consent-mode" rel="noopener noreferrer"&gt;Simo Ahava's GitHub repository&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Click on &lt;strong&gt;Add to workspace&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Since this is a Google Community Plugin, you'll need to accept adding this plugin to your workspace. Remember, you can always review the source code in the plugin repository.&lt;/p&gt;

&lt;p&gt;If you agree, click on the &lt;strong&gt;Add&lt;/strong&gt; button.&lt;/p&gt;

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

&lt;p&gt;You will then be presented with the Tag Configuration menu.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Consent Command&lt;/strong&gt; select field, choose &lt;strong&gt;Update&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Now, we can select which consent settings we want to update after the User's approval.&lt;/p&gt;

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

&lt;p&gt;Cookie Consent gives us the option to select "Performance and Analytics" cookies and "Advertisement and Targeting" cookies. To accomplish this, we created two GTM Variables called "Consent - Analytics" and "Consent - Targeting". Now, we can use them in this plugin to determine whether a specific consent was denied or granted.&lt;/p&gt;

&lt;p&gt;Click on the "Advertising" select field and choose &lt;code&gt;{{Consent - Targeting}}&lt;/code&gt;. Then click on "Analytics" and choose &lt;code&gt;{{Consent - Analytics}}&lt;/code&gt;. Set the rest of the fields to &lt;code&gt;denied&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After making these changes, the settings should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubufh2weffahpr1sz6fn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubufh2weffahpr1sz6fn.png" alt="Select Prepared GTM Variables in Consent Mode Tag Configuration" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll down and select triggers. We need to select &lt;strong&gt;Client Consent Update&lt;/strong&gt; and &lt;strong&gt;Consent Initialization - All Pages&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; To add multiple triggers to one tag, click on the "+" (plus) icon that appears after selecting the first one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The final result should look like this:&lt;/p&gt;

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

&lt;p&gt;Finally, rename this tag to &lt;code&gt;Consent Mode | Update Consent&lt;/code&gt; and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, if we go to the Tag Assistant to test it out, we will see that after clicking on any consent-related button on our page, a new event will be displayed.&lt;/p&gt;

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

&lt;p&gt;However, please note that the consent itself will be updated after a short delay. In this example, you may observe that the &lt;code&gt;Consent&lt;/code&gt; event (which handles consent updates in GTM) is labeled as number 20, whereas the &lt;code&gt;client-consent-update&lt;/code&gt; event is number 18.&lt;/p&gt;

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

&lt;p&gt;This is why we need the previously added "GTM Consent Updated" trigger.&lt;/p&gt;

&lt;p&gt;Once again, open the &lt;strong&gt;Tags&lt;/strong&gt; menu and click on the &lt;strong&gt;New&lt;/strong&gt; button. Then click on &lt;strong&gt;Tag Configuration&lt;/strong&gt; and select &lt;strong&gt;Custom HTML&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This tag will be activated whenever the user clicks on any button within our Cookie Consent. After a brief delay of half a second (value: 500), it will trigger an additional event named &lt;code&gt;gtm-consent-updated&lt;/code&gt;. We will link this trigger to our "consent blocked" tags, such as Google Analytics or Facebook Pixel. This step is necessary because GTM consent updates occur slightly later and not immediately after our &lt;code&gt;client-consent-update&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;Paste the code below into the &lt;strong&gt;HTML&lt;/strong&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  (function() {
    window.setTimeout(function () { window.dataLayer.push({ 'event': 'gtm-consent-updated' }); }, 500);
  })();
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;After that, select &lt;strong&gt;Client Consent Update&lt;/strong&gt; in the trigger section below.&lt;/p&gt;

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

&lt;p&gt;Rename the newly created tag to &lt;code&gt;GTM Consent Update&lt;/code&gt; and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That covers the GTM configuration. Now, let's add an example that uses Google Analytics tag, and see if it will be added to the User's session only after the cookie consent.&lt;/p&gt;

&lt;p&gt;[CTA4]&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Tags to Comply with Cookie Consent
&lt;/h2&gt;

&lt;p&gt;In this chapter, it's important to note that Google has introduced Tags with built-in support for consent mode, and Google Analytics is one such product. This means that you don't need to add an additional consent check. To read more about Google products' consent mode behavior, check out this &lt;a href="https://support.google.com/analytics/answer/9976101?hl=en" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this chapter, I'll also show you how to configure consent checks for third-party Tags, so only Users who have given their consent will receive tracking cookies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Analytics 4 Built-in Consent Mode
&lt;/h3&gt;

&lt;p&gt;Let's start by adding the Google Analytics 4 Tag to the GTM Demo Container and seeing how the built-in consent checks work.&lt;/p&gt;

&lt;p&gt;First, select the &lt;strong&gt;Tags&lt;/strong&gt; option from the left-side menu and click the &lt;strong&gt;New&lt;/strong&gt; button. &lt;/p&gt;

&lt;p&gt;Then choose &lt;strong&gt;Tag Configuration&lt;/strong&gt; and select &lt;strong&gt;Google Analytics GA4 Configuration&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Enter your &lt;a href="https://support.google.com/analytics/answer/9539598?hl=en" rel="noopener noreferrer"&gt;GA Measurement ID&lt;/a&gt; into the input field.&lt;/p&gt;

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

&lt;p&gt;Next, navigate to the &lt;strong&gt;Advanced Settings&lt;/strong&gt; section and access the &lt;strong&gt;Consent Settings&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;As you can see below, the Google Analytics 4 Tag has built-in consent checks for &lt;code&gt;ad_storage&lt;/code&gt; and &lt;code&gt;analytics_storage&lt;/code&gt;. This means that we don't need to add anything more to this tag, and the Google Analytics 4 cookie will not be added to the User's page unless they provide the necessary consents.&lt;/p&gt;

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

&lt;p&gt;Because we've just checked consent settings, we can select the &lt;strong&gt;No additional consent required&lt;/strong&gt; option. Google added this option to help Users distinguish if the specific tag consents were reviewed or not.&lt;/p&gt;

&lt;p&gt;Now let's configure the triggers that will activate this tag. &lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Triggering&lt;/strong&gt; and select both &lt;strong&gt;All pages&lt;/strong&gt; and &lt;strong&gt;GTM Consent Updated&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Finally, click the &lt;strong&gt;Save&lt;/strong&gt; button. After performing this action, GTM will suggest a new name, such as "Google Analytics GA4 Configuration." Let's accept it as is.&lt;/p&gt;

&lt;p&gt;To test the setup, let's enter "Preview" mode.&lt;/p&gt;

&lt;p&gt;If you clear your browser cookies, refresh the page with the GTM Tag, and closely inspect the Tag Assistant, you will notice that the Google Analytics Tag was fired after the Container loaded, but because the consent status was denied, no tracking cookies were added to the User's browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsfdht9qr4dygmap73l18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsfdht9qr4dygmap73l18.png" alt="Tag Assistant - Check Tag Execution" width="800" height="283"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48iqxlzn8qz68rqbgiql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48iqxlzn8qz68rqbgiql.png" alt="Tag Assistant - Check GTM Consent Values" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Checking the cookies on the User's side will reveal that nothing except the &lt;code&gt;__TAG_ASSISTANT&lt;/code&gt; cookie is added to the User's browser.&lt;/p&gt;

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

&lt;p&gt;Now let's refresh the page and click on the &lt;strong&gt;Accept all&lt;/strong&gt; button in the consent banner, and you will observe new entries in the Tag Manager.&lt;/p&gt;

&lt;p&gt;After the User clicks the button, GTM will receive the &lt;code&gt;client-consent-update&lt;/code&gt; trigger, and after a short delay, the consents will be updated. Subsequently, the &lt;code&gt;gtm-consent-updated&lt;/code&gt; trigger will once again fire the Google Analytics tag.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqr6160cw28z1kk8ijurm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqr6160cw28z1kk8ijurm.png" alt="Tag Assistant - Check Tag Execution After User's Cookie Consent" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But this time, the Consent State of &lt;code&gt;ad_storage&lt;/code&gt; and &lt;code&gt;analytics_storage&lt;/code&gt; will be equal to granted. This means that Google Analytics 4 will add tracking cookies to the User's browser.&lt;/p&gt;

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

&lt;p&gt;Refreshing the cookie list without refreshing the page will confirm that everything is functioning properly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx1fx52cyz0vp9peyqkz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx1fx52cyz0vp9peyqkz.png" alt="Check User's Browser Cookies After Cookie Consent" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's explore another scenario: what happens if a User previously accepted all cookies and revisits the site? To answer this question refresh the browser window and examine the new entries in the Tag Manager.&lt;/p&gt;

&lt;p&gt;As shown in the screenshot below, this time the Google Analytics Tag will be automatically activated because both needed consents were granted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn21gx2vvdbvggrfnyagg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn21gx2vvdbvggrfnyagg.png" alt="Tag Assistant - GTM Consent Values After User's Browser Refresh" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! We have successfully tested out Google's built-in consent configuration. Now, only Users who accept our analytical cookies will be tracked, respecting the privacy of others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration of 3rd Party Tag Consent Mode
&lt;/h3&gt;

&lt;p&gt;To illustrate the use of 3rd party tags, we will take the example of Hotjar, which doesn't have built-in consent checks.&lt;/p&gt;

&lt;p&gt;First, click on &lt;strong&gt;Tags&lt;/strong&gt; from the left-side menu and then click on &lt;strong&gt;New&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Next, select &lt;strong&gt;Tag Configuration&lt;/strong&gt; and click on the search icon at the top right corner. Type &lt;code&gt;Hotjar&lt;/code&gt; and select &lt;strong&gt;Hotjar Tracking Code&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Enter the Hotjar Site ID.&lt;/p&gt;

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

&lt;p&gt;Then navigate to the &lt;strong&gt;Advanced Settings&lt;/strong&gt; section and access the &lt;strong&gt;Consent Settings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Choose the &lt;strong&gt;Require additional consent for tag to fire&lt;/strong&gt; option, click on &lt;strong&gt;Add required consent&lt;/strong&gt;, and select the &lt;code&gt;analytics_storage&lt;/code&gt; option from the select field.&lt;/p&gt;

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

&lt;p&gt;This ensures that Hotjar will only be added to the User's page after they provide the necessary analytics consent.&lt;/p&gt;

&lt;p&gt;Similarly, you can configure the consent check for any other third-party Tag you add to GTM.&lt;/p&gt;

&lt;p&gt;Now, let's configure the triggers that will activate this tag.&lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Triggering&lt;/strong&gt; and select both &lt;strong&gt;All pages&lt;/strong&gt; and &lt;strong&gt;GTM Consent Updated&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Finally, click the &lt;strong&gt;Save&lt;/strong&gt; button.  After performing this action, GTM will suggest a new name, such as "Hotjar Tracking Code." Let's accept it as is.&lt;/p&gt;

&lt;p&gt;To test the setup, let's enter "Preview" mode.&lt;/p&gt;

&lt;p&gt;If you clear your browser cookies and refresh the page with the GTM Tag, you'll notice that the Hotjar Tracking Code is blocked by the Consent Settings once the Container is loaded. Although this behavior differs slightly from the built-in consent checks, the end result remains the same. The Hotjar Tracking Cookie will only be added to the User's browser after their consent is given.&lt;/p&gt;

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

&lt;p&gt;Now, click on the &lt;strong&gt;Accept all&lt;/strong&gt; button in the consent banner, and you will see new entries in the Tag Manager. After selecting the &lt;code&gt;gtm-consent-updated&lt;/code&gt; entry, you'll see that this time the Hotjar Tag was fired.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4jlxfsv03pn7463l5go.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4jlxfsv03pn7463l5go.png" alt="Tag Assistant - Check Hotjar Tag Execution After User's Cookie Consent" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refreshing the cookie list without refreshing the page will confirm that Hotjar tracking cookies were received only after the User's consent.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; For security reasons, Hotjar only operates and adds its cookies to the User's browser when accessed over HTTPS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Consent Overview
&lt;/h3&gt;

&lt;p&gt;Consent Overview is the feature that helps confirm whether all added Tags are compliant with cookie policies. To access the GTM Consent Overview menu, select &lt;strong&gt;Tags&lt;/strong&gt; from the menu on the left-hand side and click on the &lt;strong&gt;Shield Icon&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;When you access the GTM Consent Overview menu, you'll see a view that displays all the Tags you've added to your container.&lt;/p&gt;

&lt;p&gt;The first table shows the Tags for which consent hasn't been configured. In this case, the table shows only technical Tags that help receive and update User consents, and do not require any specific configuration.&lt;/p&gt;

&lt;p&gt;The second table lists all Tags with configured consents. In the previous steps, we selected "No additional consent required" for the Google Analytics Tag and "analytics_storage" for Hotjar, which is why these two tags appear in this list.&lt;/p&gt;

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

&lt;p&gt;To simplify things, we can choose the "No additional consent required" option in the "Advanced settings/Consent settings" menu for every tag that doesn't require additional consent. This will make it easier for us to identify any new Tags without configured consent in the future.&lt;/p&gt;

&lt;p&gt;The final result will look as follows:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Publishing Your GTM Container
&lt;/h2&gt;

&lt;p&gt;Once you've completed your review, you can close the window and proceed to submit the container configuration.&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;Submit&lt;/strong&gt; button available at the top right corner.&lt;/p&gt;

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

&lt;p&gt;Enter a "Version Name", in this example I will use &lt;code&gt;1.0.0&lt;/code&gt; semantic versioning compliant string, and then click &lt;strong&gt;Publish&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;From this point on, every page with this specific Google Tag Manager script will display a cookie consent banner and add Google Analytics and Hotjar only for Users who have given their consent.&lt;/p&gt;

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

&lt;p&gt;Thanks to the version control feature, you'll be able to easily publish new versions with the latest changes and revert to previous versions if needed, without any hassle.&lt;/p&gt;

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

&lt;p&gt;I hope that the amount of effort needed to configure GTM Consent Mode would not discourage you from using this method of handling User's consent.&lt;/p&gt;

&lt;p&gt;Note that once properly setup, management of a large number of Tags through the GTM UI is simplified,  without the need to modify the code or rely on your technical team for assistance. Adding a new Tag to the existing configuration is a very straightforward process. Simply select the Tag from the existing options or choose a Custom HTML Tag, provide the necessary identifier or script, and specify the required consents for the Tag to be activated. That's it!&lt;/p&gt;

&lt;p&gt;Also remember that you can always export this container configuration as a &lt;code&gt;.json&lt;/code&gt; file and reuse it in your future projects.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>compliance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Manage user cookie consent with Google Tag Manager: Adapting to CookieConsent v3</title>
      <dc:creator>uninterrupted</dc:creator>
      <pubDate>Mon, 14 Jul 2025 07:00:00 +0000</pubDate>
      <link>https://forem.com/u11d/manage-user-cookie-consent-with-google-tag-manager-adapting-to-cookieconsent-v3-2pl</link>
      <guid>https://forem.com/u11d/manage-user-cookie-consent-with-google-tag-manager-adapting-to-cookieconsent-v3-2pl</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;With the release of &lt;a href="https://cookieconsent.orestbida.com/" rel="noopener noreferrer"&gt;CookieConsent v3&lt;/a&gt;, we've decided to create this article to help you understand and adapt to the new version. This article builds on concepts and areas discussed in our previous post. For a deeper dive and to see the full adaptation process, please read our previous &lt;a href="https://u11d.com/blog/manage-user-cookie-consent-with-google-tag-manager-a-step-by-step-guide/" rel="noopener noreferrer"&gt;step-by-step guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a Cookie Consent Banner
&lt;/h2&gt;

&lt;p&gt;First, let's look at the changes in the default configuration of Cookie Consent. Here's an example of the code you need to paste into &lt;strong&gt;Custom HTML&lt;/strong&gt; in &lt;strong&gt;Tag Configuration&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt;
  &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@3.0.1/dist/cookieconsent.umd.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;CookieConsent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// https://cookieconsent.orestbida.com/reference/configuration-reference.html#guioptions&lt;/span&gt;
    &lt;span class="na"&gt;guiOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;consentModal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloud inline&lt;/span&gt;&lt;span class="dl"&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="s1"&gt;bottom left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;equalWeightButtons&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;flipButtons&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="na"&gt;preferencesModal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;box&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;equalWeightButtons&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;flipButtons&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="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;onFirstConsent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// callback triggered only once on the first accept/reject action&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;necessary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;enabled&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="c1"&gt;// this category is enabled by default&lt;/span&gt;
        &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;// this category cannot be disabled&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;autoClear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;cookies&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^_ga/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// regex: match all cookies starting with '_ga'&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_gid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// string: exact cookie name&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;// https://cookieconsent.orestbida.com/reference/configuration-reference.html#category-services&lt;/span&gt;
        &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;ga&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Google Analytics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;cookies&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;_ga|_gid&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&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="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;targeting&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="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;consentModal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;We use cookies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cookie modal description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;acceptAllBtn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;acceptNecessaryBtn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reject all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;showPreferencesBtn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Manage Individual preferences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;preferencesModal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Manage cookie preferences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;acceptAllBtn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;acceptNecessaryBtn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reject all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;savePreferencesBtn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept current selection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;closeIconLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Close modal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;sections&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cookie usage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;We use cookies to ensure the basic functionalities of the website and to enhance your online experience ...&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Strictly necessary cookies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;These cookies are essential for the proper functioning of my website. Without these cookies, the website would not work properly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;linkedCategory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;necessary&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Performance and Analytics cookies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;These cookies allow the website to remember the choices you have made in the past&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;linkedCategory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;cookieTable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Service&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="na"&gt;expiration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expiration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="na"&gt;body&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_ga&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Google Analytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cookie set by &amp;lt;a href=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;#das&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;Google Analytics&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;expiration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expires after 12 days&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_gid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Google Analytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cookie set by &amp;lt;a href=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;#das&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;Google Analytics&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;expiration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Session&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="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Targeting and Advertising&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;These cookies are used to make advertising messages more relevant to you and your interests. The intention is to display ads that are relevant and engaging for the individual user and thereby more valuable for publishers and third party advertisers.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;linkedCategory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;targeting&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="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;More information&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;For any queries in relation to my policy on cookies and your choices, please &amp;lt;a href="#contact-page"&amp;gt;contact us&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="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="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you preview your application through the GTM panel, you should see a banner in the bottom left corner of the page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojtaqudux4gp497kzmhc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojtaqudux4gp497kzmhc.png" alt="Cookie Consent Banner in the User's Browser" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring GTM Variables
&lt;/h2&gt;

&lt;p&gt;When reading a cookie, it's possible that you might receive a string instead of an object when trying to read it by name.&lt;/p&gt;

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

&lt;p&gt;To prevent this, make sure to check the &lt;code&gt;URI-decode cookie&lt;/code&gt; box when adding a &lt;strong&gt;1st Party Cookie&lt;/strong&gt; in &lt;strong&gt;Variable Configuration&lt;/strong&gt;:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Consent Variables
&lt;/h2&gt;

&lt;p&gt;The script we presented earlier for reading the selected field in the banner works correctly for selecting an entire category.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function() {
  var cookie = {{CC Cookie}}
  if (!cookie) { return 'denied'; }
  var json = JSON.parse(cookie)
  if (json["categories"].includes("analytics")) {
    return "granted";
  } else {
    return 'denied';
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this article, we'll stick with this configuration. However, Cookie Consent also allows for the configuration of acceptance for individual services if they have been added to the configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// https://cookieconsent.orestbida.com/reference/configuration-reference.html#category-services
services: {
  ga: {
    label: 'Google Analytics',
    cookies: [
      {
        name: /^(_ga|_gid)/
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;If you want to create variables for individual services, replace the condition in the script. For example &lt;code&gt;json["categories"].includes("analytics")&lt;/code&gt; to &lt;code&gt;json["services"]["analytics"].includes("ga")&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Note that even if there are no services in a given category, the services object will contain an array for that category, which will be empty.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Updating GTM Consents
&lt;/h2&gt;

&lt;p&gt;Another update involves renaming fields in &lt;strong&gt;Consent Mode (Google tags)&lt;/strong&gt;. These fields now appear as follows:&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; You can also utilize the functionality of services to assign the appropriate consent to individual fields.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Thank you for reading! For more detailed steps and additional information, make sure to check out our &lt;a href="https://u11d.com/blog/manage-user-cookie-consent-with-google-tag-manager-a-step-by-step-guide/" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;. If you have any questions or need further assistance, feel free to reach out.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>privacy</category>
      <category>compliance</category>
    </item>
  </channel>
</rss>
