<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://docs.sadeeminfo.com/blog</id>
    <title>Sadeem Informatique Blog</title>
    <updated>2026-03-02T10:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://docs.sadeeminfo.com/blog"/>
    <subtitle>Sadeem Informatique Blog</subtitle>
    <icon>https://docs.sadeeminfo.com/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[Production-Ready Dockerfiles for NestJS: Caching, Multi-Stage Builds & Security]]></title>
        <id>https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide</id>
        <link href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide"/>
        <updated>2026-03-02T10:00:00.000Z</updated>
        <summary type="html"><![CDATA[Learn how to write a production-grade Dockerfile for your NestJS app with smart caching, Prisma client generation, production-only dependencies, and non-root runtime security.]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>For developers at <strong>Sadeem informatique</strong></p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://miro.medium.com/1*KSaKpNjCfrvOhTR8BGX5cg.jpeg" alt="NestJS Docker setup illustration" class="img_ev3q"></p>
<p><em>Image source: <a href="https://nestjs.com/" target="_blank" rel="noopener noreferrer" class="">NestJS</a> official assets.</em></p>
<p>Getting Docker working with NestJS is easy. Getting it right with proper layer caching, deterministic installs, Prisma client generation, and a secure runtime image takes a bit more structure. This guide walks through each Dockerfile decision so you can adapt it safely to your own services.</p>
<p>By the end, you'll have a Dockerfile that:</p>
<ul>
<li class="">Maximises Docker layer caching so rebuilds are fast</li>
<li class="">Never reinstalls <code>node_modules</code> when only application code changes</li>
<li class="">Generates Prisma client in the build stage</li>
<li class="">Uses multi-stage builds to keep the final image lean</li>
<li class="">Runs as a non-root user in production</li>
</ul>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Shared responsibility</div><div class="admonitionContent_BuS1"><p>Even if Dockerfile ownership sits mostly with DevOps, backend developers should still understand container build fundamentals. It makes debugging CI/CD issues faster and keeps deployment constraints visible during feature work.</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Local development note</div><div class="admonitionContent_BuS1"><p>This guide targets a production Dockerfile. For day-to-day local development and testing, use a separate <code>Dockerfile.local</code> tailored for fast iteration (for example, bind mounts, hot reload, and dev dependencies).</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-full-dockerfile">The Full Dockerfile<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#the-full-dockerfile" class="hash-link" aria-label="Direct link to The Full Dockerfile" title="Direct link to The Full Dockerfile" translate="no">​</a></h2>
<p>Here's the complete file we'll walk through first, then break down stage by stage.</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">Dockerfile</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Base image</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM node:22-alpine AS base</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /usr/src/app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Dependencies (build deps)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS deps</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY package.json package-lock.json ./</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm ci</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Build stage</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS builder</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /usr/src/app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NODE_MEMORY=512</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_OPTIONS="--max-old-space-size=${NODE_MEMORY}"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=deps /usr/src/app/node_modules ./node_modules</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npx prisma generate</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm run build</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Production dependencies only</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS prod-deps</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY package.json package-lock.json ./</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm ci --omit=dev &amp;&amp; npm cache clean --force</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Runtime image</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS runner</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /usr/src/app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG PORT=3000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG RUNTIME_NODE_MEMORY=384</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_ENV=production</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV PORT=${PORT}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_OPTIONS="--enable-source-maps --max-old-space-size=${RUNTIME_NODE_MEMORY}"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN addgroup -S appgroup &amp;&amp; adduser -S appuser -G appgroup</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=appuser:appgroup /usr/src/app/dist ./dist</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=prod-deps --chown=appuser:appgroup /usr/src/app/node_modules ./node_modules</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=appuser:appgroup /usr/src/app/package.json ./package.json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=appuser:appgroup /usr/src/app/prisma ./prisma</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">USER appuser</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">EXPOSE ${PORT}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CMD ["node", "dist/main.js"]</span><br></span></code></pre></div></div>
<p>If your project does not use Prisma, remove <code>npx prisma generate</code>, the <code>prisma/</code> copy step, and the migration script section.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-1-the-base-image">Stage 1: The Base Image<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#stage-1-the-base-image" class="hash-link" aria-label="Direct link to Stage 1: The Base Image" title="Direct link to Stage 1: The Base Image" translate="no">​</a></h2>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM node:22-alpine AS base</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /usr/src/app</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-node22-alpine">Why <code>node:22-alpine</code>?<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#why-node22-alpine" class="hash-link" aria-label="Direct link to why-node22-alpine" title="Direct link to why-node22-alpine" translate="no">​</a></h3>
<p>For many NestJS services, Alpine provides a good size/performance balance. If your stack has native dependencies that require <code>glibc</code>, switch to <code>node:22-slim</code> for compatibility.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="naming-the-stage-with-as-base">Naming the stage with <code>AS base</code><a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#naming-the-stage-with-as-base" class="hash-link" aria-label="Direct link to naming-the-stage-with-as-base" title="Direct link to naming-the-stage-with-as-base" translate="no">​</a></h3>
<p>Using a shared <code>base</code> stage gives one source of truth for Node version and workdir across all later stages.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-2-the-dependency-layer-docker-caching-done-right">Stage 2: The Dependency Layer, Docker Caching Done Right<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#stage-2-the-dependency-layer-docker-caching-done-right" class="hash-link" aria-label="Direct link to Stage 2: The Dependency Layer, Docker Caching Done Right" title="Direct link to Stage 2: The Dependency Layer, Docker Caching Done Right" translate="no">​</a></h2>
<p>This is the most important caching decision in the file.</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS deps</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY package.json package-lock.json ./</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm ci</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-cache-invalidation">The problem: cache invalidation<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#the-problem-cache-invalidation" class="hash-link" aria-label="Direct link to The problem: cache invalidation" title="Direct link to The problem: cache invalidation" translate="no">​</a></h3>
<p>Docker caches layer by layer. If a layer changes, every following layer is invalidated. If you copy all source before installing dependencies, Docker reinstalls packages on every code change.</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Bad pattern</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm ci</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm run build</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-solution-isolate-dependency-installation">The solution: isolate dependency installation<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#the-solution-isolate-dependency-installation" class="hash-link" aria-label="Direct link to The solution: isolate dependency installation" title="Direct link to The solution: isolate dependency installation" translate="no">​</a></h3>
<p>By copying only manifest files in the dependency stage, Docker reuses <code>npm ci</code> unless dependency files change.</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Good pattern</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY package.json package-lock.json ./</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm ci</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-npm-ci-instead-of-npm-install">Why <code>npm ci</code> instead of <code>npm install</code>?<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#why-npm-ci-instead-of-npm-install" class="hash-link" aria-label="Direct link to why-npm-ci-instead-of-npm-install" title="Direct link to why-npm-ci-instead-of-npm-install" translate="no">​</a></h3>
<p><code>npm ci</code> is deterministic and CI-friendly:</p>
<ul>
<li class="">Installs exactly what <code>package-lock.json</code> defines</li>
<li class="">Fails if lockfile and manifest are out of sync</li>
<li class="">Avoids implicit upgrades</li>
<li class="">Produces reproducible builds across environments</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-3-the-build-stage-no-hardcoded-values">Stage 3: The Build Stage, No Hardcoded Values<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#stage-3-the-build-stage-no-hardcoded-values" class="hash-link" aria-label="Direct link to Stage 3: The Build Stage, No Hardcoded Values" title="Direct link to Stage 3: The Build Stage, No Hardcoded Values" translate="no">​</a></h2>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS builder</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /usr/src/app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NODE_MEMORY=512</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_OPTIONS="--max-old-space-size=${NODE_MEMORY}"</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="build-arguments-over-hardcoded-values">Build arguments over hardcoded values<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#build-arguments-over-hardcoded-values" class="hash-link" aria-label="Direct link to Build arguments over hardcoded values" title="Direct link to Build arguments over hardcoded values" translate="no">​</a></h3>
<p>Hardcoding configuration in images creates environment-specific artifacts. Prefer <code>ARG</code> defaults that can be overridden during build.</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ARG NODE_MEMORY=512</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_OPTIONS="--max-old-space-size=${NODE_MEMORY}"</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="passing-build-args">Passing build args<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#passing-build-args" class="hash-link" aria-label="Direct link to Passing build args" title="Direct link to Passing build args" translate="no">​</a></h3>
<div class="theme-tabs-container tabs-container tabList__CuJ"><ul role="tablist" aria-orientation="horizontal" class="tabs"><li role="tab" tabindex="0" aria-selected="true" class="tabs__item tabItem_LNqP tabs__item--active">Docker CLI</li><li role="tab" tabindex="-1" aria-selected="false" class="tabs__item tabItem_LNqP">Docker Compose</li><li role="tab" tabindex="-1" aria-selected="false" class="tabs__item tabItem_LNqP">GitHub Actions</li></ul><div class="margin-top--md"><div role="tabpanel" class="tabItem_Ymn6"><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">docker build \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg NODE_MEMORY=1024 \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  -t nest-app:staging .</span><br></span></code></pre></div></div></div><div role="tabpanel" class="tabItem_Ymn6" hidden=""><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">docker-compose.yml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">services</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">api</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">build</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">context</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">args</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">NODE_MEMORY</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><br></span></code></pre></div></div></div><div role="tabpanel" class="tabItem_Ymn6" hidden=""><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">.github/workflows/deploy.yml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Build Docker image</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    docker build \</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      --build-arg NODE_MEMORY=1024 \</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      -t nest-app:${{ github.sha }} .</span><br></span></code></pre></div></div></div></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="bringing-in-node_modules-from-the-deps-stage">Bringing in <code>node_modules</code> from the deps stage<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#bringing-in-node_modules-from-the-deps-stage" class="hash-link" aria-label="Direct link to bringing-in-node_modules-from-the-deps-stage" title="Direct link to bringing-in-node_modules-from-the-deps-stage" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=deps /usr/src/app/node_modules ./node_modules</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npx prisma generate</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm run build</span><br></span></code></pre></div></div>
<p>The stage reuses cached dependencies and then compiles NestJS to <code>dist/</code>. Prisma generation belongs here because it depends on schema and build-time tooling.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tsconfigbuildjson-for-predictable-dist-output"><code>tsconfig.build.json</code> for predictable <code>dist/</code> output<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#tsconfigbuildjson-for-predictable-dist-output" class="hash-link" aria-label="Direct link to tsconfigbuildjson-for-predictable-dist-output" title="Direct link to tsconfigbuildjson-for-predictable-dist-output" translate="no">​</a></h3>
<p>Make sure your NestJS build config writes only compiled source into <code>dist</code>:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">tsconfig.build.json</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"extends"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"./tsconfig.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"compilerOptions"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"outDir"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"./dist"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"rootDir"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"./src"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"include"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"src/**/*"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"exclude"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"node_modules"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"test"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"dist"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"**/*spec.ts"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-4-the-runtime-image-lean-and-secure">Stage 4: The Runtime Image, Lean and Secure<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#stage-4-the-runtime-image-lean-and-secure" class="hash-link" aria-label="Direct link to Stage 4: The Runtime Image, Lean and Secure" title="Direct link to Stage 4: The Runtime Image, Lean and Secure" translate="no">​</a></h2>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS runner</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /usr/src/app</span><br></span></code></pre></div></div>
<p>This stage starts fresh and receives only runtime artifacts. Build tooling and source-only files stay out of production.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configurable-port-and-memory">Configurable port and memory<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#configurable-port-and-memory" class="hash-link" aria-label="Direct link to Configurable port and memory" title="Direct link to Configurable port and memory" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ARG PORT=3000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG RUNTIME_NODE_MEMORY=384</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_ENV=production</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV PORT=${PORT}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_OPTIONS="--enable-source-maps --max-old-space-size=${RUNTIME_NODE_MEMORY}"</span><br></span></code></pre></div></div>
<p>Use separate memory limits for build and runtime. Build usually needs more memory than the long-running API process.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="running-as-a-non-root-user">Running as a non-root user<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#running-as-a-non-root-user" class="hash-link" aria-label="Direct link to Running as a non-root user" title="Direct link to Running as a non-root user" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">RUN addgroup -S appgroup &amp;&amp; adduser -S appuser -G appgroup</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">USER appuser</span><br></span></code></pre></div></div>
<p>This reduces blast radius if the process is compromised.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="copying-only-what-is-needed">Copying only what is needed<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#copying-only-what-is-needed" class="hash-link" aria-label="Direct link to Copying only what is needed" title="Direct link to Copying only what is needed" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=appuser:appgroup /usr/src/app/dist ./dist</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=prod-deps --chown=appuser:appgroup /usr/src/app/node_modules ./node_modules</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=appuser:appgroup /usr/src/app/package.json ./package.json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=appuser:appgroup /usr/src/app/prisma ./prisma</span><br></span></code></pre></div></div>
<p>The runtime image includes only:</p>
<table><thead><tr><th>Path</th><th>Purpose</th></tr></thead><tbody><tr><td><code>dist/</code></td><td>Compiled NestJS output</td></tr><tr><td><code>node_modules/</code></td><td>Production-only dependencies</td></tr><tr><td><code>package.json</code></td><td>Runtime metadata</td></tr><tr><td><code>prisma/</code></td><td>Schema/migration files if needed at runtime</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="expose-and-cmd">EXPOSE and CMD<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#expose-and-cmd" class="hash-link" aria-label="Direct link to EXPOSE and CMD" title="Direct link to EXPOSE and CMD" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">EXPOSE ${PORT}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CMD ["node", "dist/main.js"]</span><br></span></code></pre></div></div>
<p>Use exec-form <code>CMD</code> for proper signal handling in containers. Keep this command focused on serving the NestJS app only; do not add migration commands to the Dockerfile <code>CMD</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dockerignore-for-clean-context"><code>.dockerignore</code> for clean context<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#dockerignore-for-clean-context" class="hash-link" aria-label="Direct link to dockerignore-for-clean-context" title="Direct link to dockerignore-for-clean-context" translate="no">​</a></h2>
<div class="language-dockerignore codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">.dockerignore</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerignore codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">node_modules</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">dist</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">.git</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">.gitignore</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Dockerfile</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npm-debug.log</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">.env</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">.env.*</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">coverage</span><br></span></code></pre></div></div>
<p>This keeps build context small and avoids leaking local files into image layers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="caching-strategy-visual-summary">Caching Strategy, Visual Summary<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#caching-strategy-visual-summary" class="hash-link" aria-label="Direct link to Caching Strategy, Visual Summary" title="Direct link to Caching Strategy, Visual Summary" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">                       ┌──────────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                       │  What changed?                               │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                       └──────────────────────────────────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                                        │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ┌───────────────────────────┼─────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ▼                           ▼                             ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    package.json / lockfile     application code only           runtime args only</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            │                           │                             │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ▼                           ▼                             ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ┌──────────────────┐        ┌──────────────────┐          ┌──────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │ deps: MISS       │        │ deps: HIT        │          │ deps: HIT        │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │ npm ci runs      │        │ cached reuse     │          │ cached reuse     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └────────┬─────────┘        └────────┬─────────┘          └────────┬─────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">             │                           │                              │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">             ▼                           ▼                              ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ┌──────────────────┐        ┌──────────────────┐          ┌──────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │ builder: MISS    │        │ builder: MISS    │          │ runner: MISS     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │ prisma + build   │        │ prisma + build   │          │ env-only rebuild │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └────────┬─────────┘        └────────┬─────────┘          └──────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">             │                           │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">             ▼                           ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ┌──────────────────┐        ┌──────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │ runner: MISS     │        │ runner: MISS     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └──────────────────┘        └──────────────────┘</span><br></span></code></pre></div></div>
<p>Key point: source-only changes should not invalidate your dependency installation layer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-and-running">Building and Running<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#building-and-running" class="hash-link" aria-label="Direct link to Building and Running" title="Direct link to Building and Running" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Build local image</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker build -t nest-app:dev .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Build with custom args</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker build \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg NODE_MEMORY=1024 \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg PORT=3000 \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  -t nest-app:staging .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Run</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker run -p 3000:3000 --env-file .env nest-app:staging</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Run on custom runtime port</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker run -p 8080:8080 -e PORT=8080 nest-app:staging</span><br></span></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Runtime vs build-time values</div><div class="admonitionContent_BuS1"><p>Build arguments (<code>ARG</code>) are fixed when image is built. Runtime environment variables (<code>-e</code>) can change per container start. Keep secrets and environment-specific runtime config as runtime env vars.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prisma-migration-note">Prisma Migration Note<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#prisma-migration-note" class="hash-link" aria-label="Direct link to Prisma Migration Note" title="Direct link to Prisma Migration Note" translate="no">​</a></h2>
<p>Backend developers should include a clear migration command in <code>package.json</code> so every environment can call the same script.</p>
<p>First, define the migration script in <code>package.json</code>:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">package.json</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"migration:run"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"prisma migrate deploy"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>Run it with your package manager of choice, for example:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">npm run migration:run</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="checklist">Checklist<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#checklist" class="hash-link" aria-label="Direct link to Checklist" title="Direct link to Checklist" translate="no">​</a></h2>
<p>Before shipping to production, verify:</p>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <code>package.json</code> and <code>package-lock.json</code> are copied before full source</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Dependencies are installed in a dedicated cached stage</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Prisma client is generated during build</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <code>tsconfig.build.json</code> outputs app code to <code>dist/</code></li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Runtime image contains only required runtime artifacts</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Runtime installs omit dev dependencies</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Container runs as a non-root user</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <code>CMD</code> uses exec form</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <code>.dockerignore</code> excludes local and sensitive files</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://docs.sadeeminfo.com/blog/nestjs-dockerfile-production-guide#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>A production-grade NestJS Dockerfile is about repeatability, speed, and operational safety. The patterns in this guide isolate dependency caching, keep runtime images lean, and apply practical security defaults so your service is easier to build, deploy, and maintain.</p>]]></content>
        <author>
            <name>Mohamed El Amine Meghni</name>
        </author>
        <category label="nestjs" term="nestjs"/>
        <category label="docker" term="docker"/>
        <category label="devops" term="devops"/>
        <category label="prisma" term="prisma"/>
        <category label="security" term="security"/>
        <category label="performance" term="performance"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[HTTP Response Status Codes — Practical Guide for Modern Web & API Development]]></title>
        <id>https://docs.sadeeminfo.com/blog/http-response-status-codes-explained</id>
        <link href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained"/>
        <updated>2026-03-01T15:30:00.000Z</updated>
        <summary type="html"><![CDATA[A frontend-focused yet full-stack practical guide to HTTP status codes — when to use each major 2xx/3xx/4xx/5xx code in REST APIs, SPA fetch/axios interceptors, error handling patterns, and best practices in 2026.]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>For developers at <strong>Sadeem Informatique</strong></p>
</blockquote>
<p>Most developers know <em>what</em> the codes mean — very few consistently use them correctly in real applications.
This guide shows <strong>practical usage patterns</strong> you will actually encounter in production React/Next.js apps + Node.js/Express/Laravel/whatever backend.</p>
<p><img decoding="async" loading="lazy" src="https://images.unsplash.com/photo-1555066931-4365d14bab8c?auto=format&amp;fit=crop&amp;q=80&amp;w=1600" alt="Laptop screen showing HTTP response codes and network tab" class="img_ev3q"></p>
<p><em>Photo by ThisIsEngineering on Unsplash</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="quick-reference--most-used-status-codes-in-2026">Quick Reference — Most Used Status Codes in 2026<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#quick-reference--most-used-status-codes-in-2026" class="hash-link" aria-label="Direct link to Quick Reference — Most Used Status Codes in 2026" title="Direct link to Quick Reference — Most Used Status Codes in 2026" translate="no">​</a></h2>
<table><thead><tr><th>Code</th><th>Meaning</th><th>Typical Use Case (Frontend + Backend)</th><th>Body?</th><th>Idempotent?</th></tr></thead><tbody><tr><td>200</td><td>OK</td><td>GET — resource returned, PUT/PATCH — updated</td><td>Yes</td><td>Yes</td></tr><tr><td>201</td><td>Created</td><td>POST — new resource created, Location header often used</td><td>Yes</td><td>No</td></tr><tr><td>204</td><td>No Content</td><td>DELETE successful, PUT/PATCH when no body needed</td><td>No</td><td>Yes</td></tr><tr><td>301</td><td>Moved Permanently</td><td>Permanent URL redirection (SEO important)</td><td>Sometimes</td><td>Yes</td></tr><tr><td>400</td><td>Bad Request</td><td>Validation failed, malformed JSON, missing required field</td><td>Yes</td><td>—</td></tr><tr><td>401</td><td>Unauthorized</td><td>Missing/invalid token, session expired</td><td>Yes</td><td>—</td></tr><tr><td>403</td><td>Forbidden</td><td>Authenticated but not allowed (role/permission)</td><td>Yes</td><td>—</td></tr><tr><td>404</td><td>Not Found</td><td>Resource does not exist</td><td>Yes/No</td><td>—</td></tr><tr><td>409</td><td>Conflict</td><td>Business rule violation (e.g. cannot delete paid invoice)</td><td>Yes</td><td>—</td></tr><tr><td>422</td><td>Unprocessable Entity</td><td>Semantic validation failed (WebDAV origin, very common now)</td><td>Yes</td><td>—</td></tr><tr><td>429</td><td>Too Many Requests</td><td>Rate limiting hit</td><td>Yes</td><td>—</td></tr><tr><td>500</td><td>Internal Server Error</td><td>Unexpected crash — never expose stack trace</td><td>Yes</td><td>—</td></tr><tr><td>502</td><td>Bad Gateway</td><td>Proxy / load balancer issue</td><td>Sometimes</td><td>—</td></tr><tr><td>503</td><td>Service Unavailable</td><td>Maintenance, overload — should include Retry-After</td><td>Yes</td><td>—</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="most-important-status-codes--practical-backend-examples">Most Important Status Codes — Practical Backend Examples<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#most-important-status-codes--practical-backend-examples" class="hash-link" aria-label="Direct link to Most Important Status Codes — Practical Backend Examples" title="Direct link to Most Important Status Codes — Practical Backend Examples" translate="no">​</a></h2>
<div class="theme-tabs-container tabs-container tabList__CuJ"><ul role="tablist" aria-orientation="horizontal" class="tabs"><li role="tab" tabindex="0" aria-selected="true" class="tabs__item tabItem_LNqP tabs__item--active">Node.js / Express</li></ul><div class="margin-top--md"><div role="tabpanel" class="tabItem_Ymn6"><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="success--post-201-created">Success — POST (201 Created)<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#success--post-201-created" class="hash-link" aria-label="Direct link to Success — POST (201 Created)" title="Direct link to Success — POST (201 Created)" translate="no">​</a></h3><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">routes/users.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/users"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> data </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> createUserSchema</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">parseAsync</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> prisma</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  res</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">header</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Location"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">/users/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">user</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">id</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">status</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">201</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="client-error--422-unprocessable-entity-preferred-over-400-for-field-validation">Client Error — 422 Unprocessable Entity (preferred over 400 for field validation)<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#client-error--422-unprocessable-entity-preferred-over-400-for-field-validation" class="hash-link" aria-label="Direct link to Client Error — 422 Unprocessable Entity (preferred over 400 for field validation)" title="Direct link to Client Error — 422 Unprocessable Entity (preferred over 400 for field validation)" translate="no">​</a></h3><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/users"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> data </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> createUserSchema</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">parseAsync</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">ZodError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">status</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">422</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Validation failed"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        errors</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">flatten</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">fieldErrors</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="409-conflict--business-rule-violation">409 Conflict — business rule violation<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#409-conflict--business-rule-violation" class="hash-link" aria-label="Direct link to 409 Conflict — business rule violation" title="Direct link to 409 Conflict — business rule violation" translate="no">​</a></h3><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">existingSubscription</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">status </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"active"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">status</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">409</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    code</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"SUBSCRIPTION_ALREADY_ACTIVE"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Cannot create trial — user already has active plan"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div></div></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend--handling-status-codes-intelligently-react--nextjs">Frontend — Handling Status Codes Intelligently (React + Next.js)<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#frontend--handling-status-codes-intelligently-react--nextjs" class="hash-link" aria-label="Direct link to Frontend — Handling Status Codes Intelligently (React + Next.js)" title="Direct link to Frontend — Handling Status Codes Intelligently (React + Next.js)" translate="no">​</a></h2>
<div class="theme-tabs-container tabs-container tabList__CuJ"><ul role="tablist" aria-orientation="horizontal" class="tabs"><li role="tab" tabindex="0" aria-selected="true" class="tabs__item tabItem_LNqP tabs__item--active">Next.js App Router + fetch</li></ul><div class="margin-top--md"><div role="tabpanel" class="tabItem_Ymn6"><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="global-fetch-wrapper-with-auto-logout-on-401">Global fetch wrapper with auto-logout on 401<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#global-fetch-wrapper-with-auto-logout-on-401" class="hash-link" aria-label="Direct link to Global fetch wrapper with auto-logout on 401" title="Direct link to Global fetch wrapper with auto-logout on 401" translate="no">​</a></h3><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">lib/api.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">"use client"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> isRefreshing </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">apiFetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  input</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">RequestInfo</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  init</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">RequestInit</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token known-class-name class-name">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">Response</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> res </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">init</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    credentials</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"include"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-property property" style="color:#36acaa">"Content-Type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"application/json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">init</span><span class="token operator" style="color:#393A34">?.</span><span class="token plain">headers</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">status</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">401</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">isRefreshing</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    isRefreshing </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// attempt silent refresh</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> refreshRes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/api/auth/refresh"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> method</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"POST"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">refreshRes</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">ok</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"refresh failed"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// retry original request</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">apiFetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> init</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// redirect to login or show session expired modal</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token dom variable" style="color:#36acaa">window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">location</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">href</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/login?session_expired=1"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">finally</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      isRefreshing </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">ok</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> errorData </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">catch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">ApiError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">status</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> errorData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">message</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Request failed"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> errorData</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">ApiError</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">public</span><span class="token plain"> data</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="using-in-server-component-vs-client-component">Using in Server Component vs Client Component<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#using-in-server-component-vs-client-component" class="hash-link" aria-label="Direct link to Using in Server Component vs Client Component" title="Direct link to Using in Server Component vs Client Component" translate="no">​</a></h3><ul>
<li class=""><strong>Server Component</strong> → use <code>status</code> directly in RSC (no throwing → error.tsx)</li>
<li class=""><strong>Client Component</strong> → throw → <code>error.tsx</code> or <code>useToast</code> / modal</li>
</ul></div></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="security--best-practices-2026">Security &amp; Best Practices (2026)<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#security--best-practices-2026" class="hash-link" aria-label="Direct link to Security &amp; Best Practices (2026)" title="Direct link to Security &amp; Best Practices (2026)" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="status-code-checklist-before-shipping">Status Code Checklist Before Shipping<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#status-code-checklist-before-shipping" class="hash-link" aria-label="Direct link to Status Code Checklist Before Shipping" title="Direct link to Status Code Checklist Before Shipping" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item">
<p><input type="checkbox" disabled=""> <!-- -->Never return 200 with <code>{ success: false }</code> — use real 4xx codes</p>
</li>
<li class="task-list-item">
<p><input type="checkbox" disabled=""> <!-- -->Use <strong>201</strong> + <strong>Location</strong> header on resource creation</p>
</li>
<li class="task-list-item">
<p><input type="checkbox" disabled=""> <!-- -->Use <strong>204</strong> for successful DELETE / bulk updates without body</p>
</li>
<li class="task-list-item">
<p><input type="checkbox" disabled=""> <!-- -->Prefer <strong>422</strong> over <strong>400</strong> for semantic/form validation errors</p>
</li>
<li class="task-list-item">
<p><input type="checkbox" disabled=""> <!-- -->Return consistent error shape on 4xx/5xx</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"status"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">422</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"code"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"VALIDATION_ERROR"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"message"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Invalid input"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"errors"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"email"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Invalid email format"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
</li>
<li class="task-list-item">
<p><input type="checkbox" disabled=""> <!-- -->Include <strong>Retry-After</strong> on 429 and 503</p>
</li>
<li class="task-list-item">
<p><input type="checkbox" disabled=""> <!-- -->Never leak stack traces / internal messages in 500 responses</p>
</li>
<li class="task-list-item">
<p><input type="checkbox" disabled=""> <!-- -->Log 5xx with correlation ID — return generic "Something went wrong"</p>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-anti-patterns-to-avoid">Common Anti-Patterns to Avoid<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#common-anti-patterns-to-avoid" class="hash-link" aria-label="Direct link to Common Anti-Patterns to Avoid" title="Direct link to Common Anti-Patterns to Avoid" translate="no">​</a></h3>
<table><thead><tr><th>Anti-pattern</th><th>Why it's bad</th><th>Better choice</th></tr></thead><tbody><tr><td>200 + <code>{ error: "..." }</code></td><td>Confuses caching, monitoring, interceptors</td><td>4xx family</td></tr><tr><td>404 for permission denied</td><td>Hides existence — security through obscurity</td><td>403 Forbidden</td></tr><tr><td>500 for validation errors</td><td>Misleads monitoring &amp; alerting</td><td>400 / 422</td></tr><tr><td>No body on 400/401/403/422</td><td>Client cannot show specific message</td><td>Always JSON body</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="glossary">Glossary<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#glossary" class="hash-link" aria-label="Direct link to Glossary" title="Direct link to Glossary" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>1xx Informational</strong> — Rare in APIs (100 Continue mostly for large uploads)</p>
</li>
<li class="">
<p><strong>2xx Success</strong> — Request completed as expected</p>
</li>
<li class="">
<p><strong>3xx Redirection</strong> — Client should take additional action (mostly handled by browser)</p>
</li>
<li class="">
<p><strong>4xx Client Error</strong> — Something wrong with the request (you fix it)</p>
</li>
<li class="">
<p><strong>5xx Server Error</strong> — Something wrong on our side (we fix it)</p>
</li>
<li class="">
<p><strong>Idempotent</strong> — Repeating the request produces the same result (GET, PUT, DELETE usually are)</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://docs.sadeeminfo.com/blog/http-response-status-codes-explained#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>HTTP status codes are <strong>not decoration</strong> — they are the first and most reliable part of the contract between frontend and backend.
Using them correctly reduces conditional logic in your React components, improves debugging speed, makes monitoring meaningful, and aligns your application with how the web was designed to work.</p>
<p>Master them — and your APIs will feel professional, predictable, and much easier to integrate with.</p>
<p>Happy coding!</p>]]></content>
        <author>
            <name>Mezaache Akram</name>
        </author>
        <category label="http" term="http"/>
        <category label="status-codes" term="status-codes"/>
        <category label="rest-api" term="rest-api"/>
        <category label="frontend" term="frontend"/>
        <category label="error-handling" term="error-handling"/>
        <category label="networking" term="networking"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Production-Ready Dockerfiles for Next.js: Caching, Multi-Stage Builds & Security]]></title>
        <id>https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide</id>
        <link href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide"/>
        <updated>2026-02-28T19:30:00.000Z</updated>
        <summary type="html"><![CDATA[Learn how to write a production-grade Dockerfile for your Next.js app covering smart layer caching, build arguments instead of hardcoded values, multi-stage builds, and running as a non-root user.]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>For developers at <strong>Sadeem informatique</strong></p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://images.pexels.com/photos/3861972/pexels-photo-3861972.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=1600" alt="Software engineer coding on a computer" class="img_ev3q"></p>
<p><em>Photo by <a href="https://www.pexels.com/@thisisengineering/" target="_blank" rel="noopener noreferrer" class="">ThisIsEngineering</a> on <a href="https://www.pexels.com/photo/female-software-engineer-coding-on-computer-3861972/" target="_blank" rel="noopener noreferrer" class="">Pexels</a>.</em></p>
<p>Getting Docker working with Next.js is easy. Getting it <em>right</em> — with proper layer caching, no hardcoded values, and a secure production image — takes a bit more thought. This guide walks you through every decision in a production-grade Dockerfile, explaining the <em>why</em> behind each choice so you can adapt it confidently to your own project.</p>
<p>By the end, you'll have a Dockerfile that:</p>
<ul>
<li class="">Maximises Docker layer caching so rebuilds are fast</li>
<li class="">Never invalidates your <code>node_modules</code> cache when only application code changes</li>
<li class="">Passes all configurable values as build arguments (no hardcoded ports or URLs)</li>
<li class="">Uses multi-stage builds to keep the final image lean</li>
<li class="">Runs as a non-root user in production</li>
</ul>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Shared responsibility</div><div class="admonitionContent_BuS1"><p>Even if Dockerfile maintenance is primarily a DevOps responsibility in your team, developers should still understand Docker fundamentals. This helps them debug build/runtime issues faster, collaborate better across teams, and ship production-ready features with fewer deployment surprises.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-full-dockerfile">The Full Dockerfile<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#the-full-dockerfile" class="hash-link" aria-label="Direct link to The Full Dockerfile" title="Direct link to The Full Dockerfile" translate="no">​</a></h2>
<p>Here's the complete file we'll be walking through. Read it once end-to-end, then we'll break it apart section by section.</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">Dockerfile</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Base image</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM node:22-slim AS base</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Dependencies (isolated for caching)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS deps</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY package.json package-lock.json ./</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm ci</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Build stage</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS builder</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NEXT_PUBLIC_BACKEND_BASE_URL</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NEXT_PUBLIC_BACKEND_SIGNED_FILE_URL</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NEXT_PUBLIC_COOKIE_DOMAIN</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NEXT_PUBLIC_USE_SECURE_COOKIES</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NODE_MEMORY=512</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NEXT_PUBLIC_BACKEND_BASE_URL=${NEXT_PUBLIC_BACKEND_BASE_URL}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NEXT_PUBLIC_BACKEND_SIGNED_FILE_URL=${NEXT_PUBLIC_BACKEND_SIGNED_FILE_URL}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NEXT_PUBLIC_COOKIE_DOMAIN=${NEXT_PUBLIC_COOKIE_DOMAIN}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NEXT_PUBLIC_USE_SECURE_COOKIES=${NEXT_PUBLIC_USE_SECURE_COOKIES}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_OPTIONS="--max-old-space-size=${NODE_MEMORY}"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NEXT_TELEMETRY_DISABLED=1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=deps /app/node_modules ./node_modules</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm run build</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Runtime image</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ===============================</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM node:22-slim AS runner</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG PORT=3005</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG RUNTIME_NODE_MEMORY=384</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_ENV=production</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NEXT_TELEMETRY_DISABLED=1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV PORT=${PORT}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_OPTIONS="--max-old-space-size=${RUNTIME_NODE_MEMORY}"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN addgroup --system --gid 1001 nodejs \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> &amp;&amp; adduser --system --uid 1001 nextjs</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=nextjs:nodejs /app/public ./public</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">USER nextjs</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">EXPOSE ${PORT}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CMD ["node", "server.js"]</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-1-the-base-image">Stage 1: The Base Image<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#stage-1-the-base-image" class="hash-link" aria-label="Direct link to Stage 1: The Base Image" title="Direct link to Stage 1: The Base Image" translate="no">​</a></h2>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM node:22-slim AS base</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /app</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-node22-slim">Why <code>node:22-slim</code>?<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#why-node22-slim" class="hash-link" aria-label="Direct link to why-node22-slim" title="Direct link to why-node22-slim" translate="no">​</a></h3>
<p>The <code>slim</code> variant strips out non-essential packages from the standard Debian image — things like build tools, documentation, and locale data you don't need at runtime. This results in a meaningfully smaller image while still being a well-maintained, official distribution.</p>
<p>Avoid <code>node:22-alpine</code> for Next.js unless you have a strong size constraint. Alpine uses <code>musl libc</code> instead of <code>glibc</code>, which can cause subtle compatibility issues with some native Node.js addons. The <code>slim</code> variant gives you most of the size benefit without the compatibility risk.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="naming-the-stage-with-as-base">Naming the stage with <code>AS base</code><a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#naming-the-stage-with-as-base" class="hash-link" aria-label="Direct link to naming-the-stage-with-as-base" title="Direct link to naming-the-stage-with-as-base" translate="no">​</a></h3>
<p>By naming this <code>base</code>, all subsequent stages can build on top of it with <code>FROM base</code>. This creates a single source of truth for the Node.js version — update it in one place and all stages pick up the change.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-2-the-dependency-layer--docker-caching-done-right">Stage 2: The Dependency Layer — Docker Caching Done Right<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#stage-2-the-dependency-layer--docker-caching-done-right" class="hash-link" aria-label="Direct link to Stage 2: The Dependency Layer — Docker Caching Done Right" title="Direct link to Stage 2: The Dependency Layer — Docker Caching Done Right" translate="no">​</a></h2>
<p>This is the most important caching decision in the entire file.</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS deps</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY package.json package-lock.json ./</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm ci</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-cache-invalidation">The problem: cache invalidation<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#the-problem-cache-invalidation" class="hash-link" aria-label="Direct link to The problem: cache invalidation" title="Direct link to The problem: cache invalidation" translate="no">​</a></h3>
<p>Docker builds images layer by layer and caches each one. When a layer changes, Docker invalidates that layer <strong>and every layer after it</strong>. This means the order of your <code>COPY</code> and <code>RUN</code> instructions matters enormously.</p>
<p>Consider this naive approach:</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># ❌ Bad — installs dependencies on EVERY code change</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm ci</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm run build</span><br></span></code></pre></div></div>
<p>Here <code>COPY . .</code> copies all your application source files first. Every time you change a single line of code, Docker sees a changed layer and re-runs <code>npm ci</code> — downloading and reinstalling hundreds of packages unnecessarily. On a modest connection, that's minutes wasted on every push.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-solution-isolate-dependency-installation">The solution: isolate dependency installation<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#the-solution-isolate-dependency-installation" class="hash-link" aria-label="Direct link to The solution: isolate dependency installation" title="Direct link to The solution: isolate dependency installation" translate="no">​</a></h3>
<p>By separating the dependency stage from the build stage, we take advantage of the fact that <code>package.json</code> and <code>package-lock.json</code> change far less frequently than your application code:</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># ✅ Good — node_modules only reinstalled when package files change</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY package.json package-lock.json ./</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm ci</span><br></span></code></pre></div></div>
<p>Docker will only re-run <code>npm ci</code> when either <code>package.json</code> or <code>package-lock.json</code> actually changes. A code-only change skips straight past this stage, pulling <code>node_modules</code> from cache in milliseconds.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-npm-ci-instead-of-npm-install">Why <code>npm ci</code> instead of <code>npm install</code>?<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#why-npm-ci-instead-of-npm-install" class="hash-link" aria-label="Direct link to why-npm-ci-instead-of-npm-install" title="Direct link to why-npm-ci-instead-of-npm-install" translate="no">​</a></h3>
<p><code>npm ci</code> (Clean Install) is purpose-built for automated environments:</p>
<ul>
<li class="">Deletes and recreates <code>node_modules</code> from scratch on every run</li>
<li class="">Installs the <em>exact</em> versions locked in <code>package-lock.json</code> (no implicit upgrades)</li>
<li class="">Fails if <code>package.json</code> and <code>package-lock.json</code> are out of sync</li>
<li class="">Never modifies <code>package-lock.json</code></li>
</ul>
<p>This guarantees deterministic, reproducible builds — essential in CI/CD pipelines and Docker.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-3-the-build-stage--no-hardcoded-values">Stage 3: The Build Stage — No Hardcoded Values<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#stage-3-the-build-stage--no-hardcoded-values" class="hash-link" aria-label="Direct link to Stage 3: The Build Stage — No Hardcoded Values" title="Direct link to Stage 3: The Build Stage — No Hardcoded Values" translate="no">​</a></h2>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM base AS builder</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NEXT_PUBLIC_BACKEND_BASE_URL</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NEXT_PUBLIC_BACKEND_SIGNED_FILE_URL</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NEXT_PUBLIC_COOKIE_DOMAIN</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NEXT_PUBLIC_USE_SECURE_COOKIES</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NODE_MEMORY=512</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="build-arguments-over-hardcoded-values">Build arguments over hardcoded values<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#build-arguments-over-hardcoded-values" class="hash-link" aria-label="Direct link to Build arguments over hardcoded values" title="Direct link to Build arguments over hardcoded values" translate="no">​</a></h3>
<p>Hardcoding environment-specific values into a Dockerfile is a common anti-pattern:</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># ❌ Bad — hardcoded, not reusable across environments</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NEXT_PUBLIC_BACKEND_BASE_URL=https://api.myapp.com</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV PORT=3005</span><br></span></code></pre></div></div>
<p>This approach creates a different Dockerfile for each environment, and worse — it may bake secrets into image layers where they persist even after being "unset". The correct pattern is <code>ARG</code>:</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># ✅ Good — values are injected at build time</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG NEXT_PUBLIC_BACKEND_BASE_URL</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NEXT_PUBLIC_BACKEND_BASE_URL=${NEXT_PUBLIC_BACKEND_BASE_URL}</span><br></span></code></pre></div></div>
<p><code>ARG</code> declares a variable that can be passed in at build time with <code>--build-arg</code>. It is <strong>not</strong> present in the final image's environment — it's only available during the build. You then promote it to <code>ENV</code> so the running application can access it.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="passing-build-args">Passing build args<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#passing-build-args" class="hash-link" aria-label="Direct link to Passing build args" title="Direct link to Passing build args" translate="no">​</a></h3>
<div class="theme-tabs-container tabs-container tabList__CuJ"><ul role="tablist" aria-orientation="horizontal" class="tabs"><li role="tab" tabindex="0" aria-selected="true" class="tabs__item tabItem_LNqP tabs__item--active">Docker CLI</li><li role="tab" tabindex="-1" aria-selected="false" class="tabs__item tabItem_LNqP">Docker Compose</li><li role="tab" tabindex="-1" aria-selected="false" class="tabs__item tabItem_LNqP">GitHub Actions</li></ul><div class="margin-top--md"><div role="tabpanel" class="tabItem_Ymn6"><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">docker build \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg NEXT_PUBLIC_BACKEND_BASE_URL=https://api.staging.myapp.com \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg NEXT_PUBLIC_COOKIE_DOMAIN=staging.myapp.com \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg NODE_MEMORY=1024 \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  -t myapp:staging .</span><br></span></code></pre></div></div></div><div role="tabpanel" class="tabItem_Ymn6" hidden=""><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">docker-compose.yml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">services</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">web</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">build</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">context</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">args</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">NEXT_PUBLIC_BACKEND_BASE_URL</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">NEXT_PUBLIC_BACKEND_BASE_URL</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">NEXT_PUBLIC_COOKIE_DOMAIN</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">NEXT_PUBLIC_COOKIE_DOMAIN</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">NODE_MEMORY</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><br></span></code></pre></div></div></div><div role="tabpanel" class="tabItem_Ymn6" hidden=""><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">.github/workflows/deploy.yml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Build Docker image</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    docker build \</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      --build-arg NEXT_PUBLIC_BACKEND_BASE_URL=${{ vars.BACKEND_URL }} \</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      --build-arg NEXT_PUBLIC_COOKIE_DOMAIN=${{ vars.COOKIE_DOMAIN }} \</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      -t myapp:${{ github.sha }} .</span><br></span></code></pre></div></div></div></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="default-values-for-non-sensitive-args">Default values for non-sensitive args<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#default-values-for-non-sensitive-args" class="hash-link" aria-label="Direct link to Default values for non-sensitive args" title="Direct link to Default values for non-sensitive args" translate="no">​</a></h3>
<p>Note that <code>NODE_MEMORY=512</code> sets a sensible default. This means developers can build locally without specifying every argument, while CI/CD pipelines can override it for resource-constrained environments:</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ARG NODE_MEMORY=512</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_OPTIONS="--max-old-space-size=${NODE_MEMORY}"</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="next_telemetry_disabled">NEXT_TELEMETRY_DISABLED<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#next_telemetry_disabled" class="hash-link" aria-label="Direct link to NEXT_TELEMETRY_DISABLED" title="Direct link to NEXT_TELEMETRY_DISABLED" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ENV NEXT_TELEMETRY_DISABLED=1</span><br></span></code></pre></div></div>
<p>Next.js collects anonymous telemetry data during builds by default. Setting this to <code>1</code> disables it in the build stage — good practice in automated pipelines both for privacy and to eliminate the small network call during every build.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="bringing-in-node_modules-from-the-deps-stage">Bringing in node_modules from the deps stage<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#bringing-in-node_modules-from-the-deps-stage" class="hash-link" aria-label="Direct link to Bringing in node_modules from the deps stage" title="Direct link to Bringing in node_modules from the deps stage" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=deps /app/node_modules ./node_modules</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN npm run build</span><br></span></code></pre></div></div>
<p>Rather than re-running <code>npm ci</code> in the builder, we pull <code>node_modules</code> directly from the <code>deps</code> stage using <code>--from=deps</code>. This keeps the two concerns cleanly separated: dependency installation happens once in its own cached stage, and the builder simply uses the result.</p>
<p>The order here is intentional: copy <code>node_modules</code> first, then copy application source. If you reversed this, a source file change would invalidate the <code>node_modules</code> copy step — which defeats the purpose.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-4-the-runtime-image--lean-and-secure">Stage 4: The Runtime Image — Lean and Secure<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#stage-4-the-runtime-image--lean-and-secure" class="hash-link" aria-label="Direct link to Stage 4: The Runtime Image — Lean and Secure" title="Direct link to Stage 4: The Runtime Image — Lean and Secure" translate="no">​</a></h2>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM node:22-slim AS runner</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /app</span><br></span></code></pre></div></div>
<p>This stage starts completely fresh from <code>node:22-slim</code>. It has no knowledge of the builder stage except for what we explicitly copy into it. This is the core benefit of multi-stage builds: your production image contains <strong>only</strong> what is needed to run the application.</p>
<p>It does not contain:</p>
<ul>
<li class="">Source code</li>
<li class=""><code>node_modules</code> (the standalone output bundles its own minimal dependencies)</li>
<li class="">Build tools</li>
<li class="">Intermediate build artifacts</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configurable-port-and-memory">Configurable port and memory<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#configurable-port-and-memory" class="hash-link" aria-label="Direct link to Configurable port and memory" title="Direct link to Configurable port and memory" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ARG PORT=3005</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ARG RUNTIME_NODE_MEMORY=384</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV PORT=${PORT}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ENV NODE_OPTIONS="--max-old-space-size=${RUNTIME_NODE_MEMORY}"</span><br></span></code></pre></div></div>
<p>The same principle applies here: no hardcoded port. The default of <code>3005</code> is used if nothing is passed, but your orchestration layer (Kubernetes, ECS, Compose) can override it for any environment.</p>
<p>Note the separate <code>RUNTIME_NODE_MEMORY</code> argument (defaulting to <code>384</code> MB) versus the build-time <code>NODE_MEMORY</code> (defaulting to <code>512</code> MB). The build process is memory-hungry — TypeScript compilation and tree-shaking for a large Next.js app can easily spike over 512 MB. The runtime process is far lighter; constraining it prevents memory bloat in production.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="running-as-a-non-root-user">Running as a non-root user<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#running-as-a-non-root-user" class="hash-link" aria-label="Direct link to Running as a non-root user" title="Direct link to Running as a non-root user" translate="no">​</a></h3>
<p>This is one of the most important security practices for production containers.</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">RUN addgroup --system --gid 1001 nodejs \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> &amp;&amp; adduser --system --uid 1001 nextjs</span><br></span></code></pre></div></div>
<p>By default, Docker containers run as <code>root</code>. This means that if your application process is ever compromised — through a vulnerability in a dependency, a deserialization flaw, or any other attack vector — the attacker has root access inside the container. Depending on how Docker is configured on the host, this can sometimes translate into host-level risk.</p>
<p>Creating a dedicated system user (<code>nextjs</code>) with a fixed UID/GID (<code>1001</code>) and then switching to it with <code>USER nextjs</code> ensures the application process has only the permissions it needs:</p>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">USER nextjs</span><br></span></code></pre></div></div>
<p>The <code>--system</code> flag creates the user/group without a home directory, a password, or login shell — appropriate for a service account.</p>
<p>The fixed GID/UID <code>1001</code> matters for file permission consistency. If your container mounts volumes or shares a filesystem with the host, predictable UIDs make permission management straightforward.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="copying-only-whats-needed">Copying only what's needed<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#copying-only-whats-needed" class="hash-link" aria-label="Direct link to Copying only what's needed" title="Direct link to Copying only what's needed" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=nextjs:nodejs /app/public ./public</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static</span><br></span></code></pre></div></div>
<p>This relies on Next.js's <code>output: 'standalone'</code> mode (configured in <code>next.config.js</code>). In standalone mode, the Next.js build produces a self-contained <code>server.js</code> that bundles only the minimal dependencies required at runtime, without the full <code>node_modules</code> tree.</p>
<p>The three things copied into the runner are:</p>
<table><thead><tr><th>Path</th><th>What it contains</th></tr></thead><tbody><tr><td><code>.next/standalone</code></td><td>The standalone server, <code>server.js</code>, and its bundled runtime dependencies</td></tr><tr><td><code>public/</code></td><td>Static assets served directly (images, fonts, etc.)</td></tr><tr><td><code>.next/static</code></td><td>Client-side JavaScript chunks and CSS</td></tr></tbody></table>
<p>Every file is <code>--chown</code>'d to the <code>nextjs:nodejs</code> user/group at copy time. This is more efficient than a separate <code>RUN chown</code> command, which would create an extra layer.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Enabling standalone mode</div><div class="admonitionContent_BuS1"><p>Make sure <code>next.config.js</code> has this set before building:</p><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">next.config.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token doc-comment comment" style="color:#999988;font-style:italic">/** </span><span class="token doc-comment comment keyword" style="color:#00009f;font-style:italic">@type</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> </span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">{</span><span class="token doc-comment comment class-name keyword" style="color:#00009f;font-style:italic">import</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">(</span><span class="token doc-comment comment class-name string" style="color:#e3116c;font-style:italic">'next'</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">)</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">.</span><span class="token doc-comment comment class-name" style="color:#999988;font-style:italic">NextConfig</span><span class="token doc-comment comment class-name punctuation" style="color:#393A34;font-style:italic">}</span><span class="token doc-comment comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> nextConfig </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'standalone'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> nextConfig</span><br></span></code></pre></div></div><p>Without this, the <code>.next/standalone</code> directory won't be produced.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="expose-and-cmd">EXPOSE and CMD<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#expose-and-cmd" class="hash-link" aria-label="Direct link to EXPOSE and CMD" title="Direct link to EXPOSE and CMD" translate="no">​</a></h3>
<div class="language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">EXPOSE ${PORT}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CMD ["node", "server.js"]</span><br></span></code></pre></div></div>
<p><code>EXPOSE</code> is documentation — it tells Docker (and anyone reading the Dockerfile) which port the container listens on. It does not actually publish the port; that happens with <code>-p</code> at runtime or in your Compose/Kubernetes config. Using the <code>$PORT</code> variable here keeps it consistent with the <code>ENV PORT</code> declaration above.</p>
<p><code>CMD ["node", "server.js"]</code> uses the exec form (a JSON array), not the shell form (<code>CMD node server.js</code>). The exec form runs <code>node</code> as PID 1, which means it receives OS signals (like <code>SIGTERM</code> for graceful shutdown) directly. The shell form wraps it in <code>/bin/sh -c</code>, which often drops those signals — a common source of containers that take 30 seconds to stop because they wait for the shutdown timeout.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="caching-strategy--a-visual-summary">Caching Strategy — A Visual Summary<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#caching-strategy--a-visual-summary" class="hash-link" aria-label="Direct link to Caching Strategy — A Visual Summary" title="Direct link to Caching Strategy — A Visual Summary" translate="no">​</a></h2>
<p>Here's how cache invalidation flows through the stages for the most common scenarios:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">                       ┌──────────────────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                       │  What changed?                                        │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                       └──────────────────────────────────────────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                                         │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ┌────────────────────────────┼────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ▼                            ▼                            ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    package.json or           Application code only          Dockerfile args only</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    package-lock.json         (e.g. a component change)      (e.g. different PORT)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            │                            │                            │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ▼                            ▼                            ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ┌───────────────┐          ┌──────────────────┐         ┌──────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │  deps: MISS   │          │  deps: HIT ✓     │         │  deps: HIT ✓     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │  npm ci runs  │          │  (cached)        │         │  (cached)        │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └───────┬───────┘          └────────┬─────────┘         └────────┬─────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            │                           │                             │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ▼                           ▼                             ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ┌───────────────┐          ┌──────────────────┐         ┌──────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │ builder: MISS │          │  builder: MISS   │         │  runner: MISS    │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │  npm run build│          │  npm run build   │         │  (rebuilt only)  │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └───────┬───────┘          └────────┬─────────┘         └──────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            │                           │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            ▼                           ▼</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ┌───────────────┐          ┌──────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │  runner: MISS │          │  runner: MISS    │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └───────────────┘          └──────────────────┘</span><br></span></code></pre></div></div>
<p>The key takeaway: <strong>only changing application code never touches the <code>deps</code> stage</strong>. Your <code>node_modules</code> layer stays cached and you skip the most time-consuming part of the build.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-and-running">Building and Running<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#building-and-running" class="hash-link" aria-label="Direct link to Building and Running" title="Direct link to Building and Running" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Build for local development</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker build -t myapp:dev .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Build for staging with custom args</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker build \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg NEXT_PUBLIC_BACKEND_BASE_URL=https://api.staging.myapp.com \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg NEXT_PUBLIC_COOKIE_DOMAIN=staging.myapp.com \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg PORT=3005 \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --build-arg NODE_MEMORY=1024 \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  -t myapp:staging .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Run the container</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker run -p 3005:3005 myapp:staging</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Run with a custom port at runtime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">docker run -p 8080:8080 -e PORT=8080 myapp:staging</span><br></span></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Runtime vs build-time environment variables</div><div class="admonitionContent_BuS1"><p><code>NEXT_PUBLIC_*</code> variables are baked into the JavaScript bundle at build time and cannot be changed at runtime — they need to be passed as <code>--build-arg</code>. Server-only environment variables (those without the <code>NEXT_PUBLIC_</code> prefix) can be injected at runtime with <code>-e</code> or via your orchestrator's secrets management.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="checklist">Checklist<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#checklist" class="hash-link" aria-label="Direct link to Checklist" title="Direct link to Checklist" translate="no">​</a></h2>
<p>Before pushing your Dockerfile to production, verify the following:</p>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <code>output: 'standalone'</code> is set in <code>next.config.js</code></li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Dependencies are installed in a separate stage from the build</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <code>package.json</code> and <code>package-lock.json</code> are copied before application source</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->No environment-specific URLs, ports, or credentials are hardcoded</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->All configurable values are declared as <code>ARG</code> with sensible defaults</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->The runner stage starts <code>FROM</code> the base image, not the builder</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->A non-root system user owns and runs the application</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <code>CMD</code> uses exec form (JSON array), not shell form</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <code>NEXT_TELEMETRY_DISABLED=1</code> is set in both builder and runner stages</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://docs.sadeeminfo.com/blog/nextjs-dockerfile-production-guide#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>A well-structured Dockerfile is not just about getting the application running in a container — it's about making your build pipeline fast, your configuration flexible, and your production environment secure. The four principles covered in this guide — smart layer caching, isolated dependency stages, build arguments over hardcoded values, and non-root execution — are straightforward to implement and pay dividends every time you push a change.</p>]]></content>
        <author>
            <name>Mohamed El Amine Meghni</name>
        </author>
        <category label="nextjs" term="nextjs"/>
        <category label="docker" term="docker"/>
        <category label="devops" term="devops"/>
        <category label="performance" term="performance"/>
        <category label="security" term="security"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Secure Bearer Token Storage in Practice]]></title>
        <id>https://docs.sadeeminfo.com/blog/secure-bearer-token-storage</id>
        <link href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage"/>
        <updated>2026-02-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A practical implementation guide for secure token handling in modern web apps — refresh tokens in HTTP-only cookies, access tokens in memory, silent refresh, and CSRF-safe configuration.]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>For developers at <strong>Sadeem informatique</strong></p>
</blockquote>
<p>Most articles explain <em>where</em> to store tokens. This guide shows you <strong>exactly how to implement a secure setup</strong> in a real web application.</p>
<p>We'll build the modern recommended pattern:</p>
<table><thead><tr><th>Token</th><th>Storage</th><th>Lifetime</th></tr></thead><tbody><tr><td>Access token</td><td>In-memory only (encrypted JWT)</td><td>15 minutes</td></tr><tr><td>Refresh token</td><td>HTTP-only encrypted cookie</td><td>7 days</td></tr></tbody></table>
<p>This approach aligns with <a href="https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html" target="_blank" rel="noopener noreferrer" class="">OWASP security guidance</a> and is widely used in production SaaS applications.</p>
<p><img decoding="async" loading="lazy" src="https://images.pexels.com/photos/4973899/pexels-photo-4973899.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=1600" alt="Laptop displaying a lock icon representing secure token and authentication practices" class="img_ev3q"></p>
<p><em>Photo by <a href="https://www.pexels.com/photo/a-laptop-over-a-round-table-4973899/" target="_blank" rel="noopener noreferrer" class="">Dan Nelson</a> on Pexels.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-overview">Architecture Overview<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#architecture-overview" class="hash-link" aria-label="Direct link to Architecture Overview" title="Direct link to Architecture Overview" translate="no">​</a></h2>
<p>The authentication flow works as follows:</p>
<ol>
<li class="">User logs in</li>
<li class="">Server returns an <strong>encrypted access token</strong> (short-lived JWT) in the response body, and a <strong>refresh token</strong> (HTTP-only encrypted cookie)</li>
<li class="">Frontend stores the access token <strong>in memory only</strong> — never in <code>localStorage</code> or <code>sessionStorage</code></li>
<li class="">When the access token expires, the frontend silently calls <code>/refresh</code></li>
<li class="">Server decrypts and validates the refresh token cookie, then issues a new access token</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Browser                          Server</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │                                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │  POST /login                   │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├───────────────────────────────►│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │                                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │  { accessToken, expiresIn } ◄──┤  Set-Cookie: refreshToken (httpOnly)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │                                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │  accessToken → stored in memory│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │                                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │  GET /profile                  │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │  Authorization: Bearer &lt;token&gt; │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├───────────────────────────────►│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │                                │  decrypt → verify → respond</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │  { profile data } ◄────────────┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │                                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │  [token expires]               │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │                                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │  POST /refresh (cookie auto-sent)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ├───────────────────────────────►│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │  { accessToken, expiresIn } ◄──┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │                                │</span><br></span></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>No localStorage, ever</div><div class="admonitionContent_BuS1"><p>Storing tokens in <code>localStorage</code> or <code>sessionStorage</code> exposes them to XSS attacks. Any injected script can read them. In-memory storage means the token disappears on page reload and is invisible to scripts.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend-implementation">Backend Implementation<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#backend-implementation" class="hash-link" aria-label="Direct link to Backend Implementation" title="Direct link to Backend Implementation" translate="no">​</a></h2>
<div class="theme-tabs-container tabs-container tabList__CuJ"><ul role="tablist" aria-orientation="horizontal" class="tabs"><li role="tab" tabindex="0" aria-selected="true" class="tabs__item tabItem_LNqP tabs__item--active">Node.js (Express)</li></ul><div class="margin-top--md"><div role="tabpanel" class="tabItem_Ymn6"><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="install-dependencies">Install Dependencies<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#install-dependencies" class="hash-link" aria-label="Direct link to Install Dependencies" title="Direct link to Install Dependencies" translate="no">​</a></h3><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">npm install express jsonwebtoken cookie-parser cors crypto-js</span><br></span></code></pre></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="server-setup">Server Setup<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#server-setup" class="hash-link" aria-label="Direct link to Server Setup" title="Direct link to Server Setup" translate="no">​</a></h3><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">server.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> express </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"express"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> jwt </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"jsonwebtoken"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> cookieParser </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"cookie-parser"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> cors </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"cors"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">CryptoJS</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"crypto-js"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">express</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">express</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">cookieParser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">cors</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">origin</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"http://localhost:3000"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">credentials</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// required for cross-origin cookies</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="token-helpers">Token Helpers<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#token-helpers" class="hash-link" aria-label="Direct link to Token Helpers" title="Direct link to Token Helpers" translate="no">​</a></h3><p>All tokens are <strong>encrypted before leaving the server</strong>. The server decrypts them on receipt before verifying the JWT signature.</p><p>The encrypted output is <strong>URL-encoded</strong> to ensure it is safe for use in <code>Authorization</code> headers and cookies — CryptoJS AES produces Base64 output that contains <code>+</code>, <code>/</code>, and <code>=</code> characters which can cause parsing issues without encoding.</p><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">tokens.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">ACCESS_SECRET</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">ACCESS_SECRET</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">REFRESH_SECRET</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">REFRESH_SECRET</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">ENCRYPTION_KEY</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">ENCRYPTION_KEY</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> token </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> jwt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">sign</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">ACCESS_SECRET</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">expiresIn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"15m"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// URL-encode the Base64 output to make it safe for headers and cookies</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">encodeURIComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">CryptoJS</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">AES</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">encrypt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">ENCRYPTION_KEY</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateRefreshToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> token </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> jwt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">sign</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">REFRESH_SECRET</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">expiresIn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"7d"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">encodeURIComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">CryptoJS</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">AES</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">encrypt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">ENCRYPTION_KEY</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">decryptToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">encryptedToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Decode before decrypting</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> bytes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token maybe-class-name">CryptoJS</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">AES</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">decrypt</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">decodeURIComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">encryptedToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">ENCRYPTION_KEY</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> bytes</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">CryptoJS</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">enc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Utf8</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div><div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Use environment variables</div><div class="admonitionContent_BuS1"><p>Never hardcode secrets. Always load them from environment variables or a secrets manager. The examples above show the correct pattern.</p></div></div><div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Why not JWE (RFC 7516)?</div><div class="admonitionContent_BuS1"><p>This guide uses a custom AES envelope (CryptoJS + JWT) rather than the JWE standard. Both achieve encrypted tokens — the difference is interoperability. If you need standard JWE, use the <a href="https://github.com/panva/jose" target="_blank" rel="noopener noreferrer" class=""><code>jose</code></a> library instead.</p></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="routes">Routes<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#routes" class="hash-link" aria-label="Direct link to Routes" title="Direct link to Routes" translate="no">​</a></h3><div class="theme-tabs-container tabs-container tabList__CuJ"><ul role="tablist" aria-orientation="horizontal" class="tabs"><li role="tab" tabindex="0" aria-selected="true" class="tabs__item tabItem_LNqP tabs__item--active">POST /login</li><li role="tab" tabindex="-1" aria-selected="false" class="tabs__item tabItem_LNqP">POST /refresh</li><li role="tab" tabindex="-1" aria-selected="false" class="tabs__item tabItem_LNqP">GET /profile</li></ul><div class="margin-top--md"><div role="tabpanel" class="tabItem_Ymn6"><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">routes/login.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/login"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Replace with real user lookup + password verification</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"dev@example.com"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> refreshToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateRefreshToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">cookie</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"refreshToken"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> refreshToken</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">httpOnly</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">       </span><span class="token comment" style="color:#999988;font-style:italic">// not accessible via JavaScript</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">secure</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">         </span><span class="token comment" style="color:#999988;font-style:italic">// HTTPS only</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">sameSite</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"strict"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// no cross-site sending (see note below)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/refresh"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">     </span><span class="token comment" style="color:#999988;font-style:italic">// scoped to the refresh endpoint only</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Return expiresIn so the frontend can schedule a proactive refresh</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> accessToken</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">expiresIn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">15</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">60</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div><div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>sameSite and OAuth/SSO</div><div class="admonitionContent_BuS1"><p><code>sameSite: "strict"</code> drops the cookie on cross-site redirects, which will break OAuth or SSO flows (e.g. "Sign in with Google"). If you add a third-party identity provider later, change this to <code>"lax"</code>.</p></div></div></div><div role="tabpanel" class="tabItem_Ymn6" hidden=""><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">routes/refresh.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/refresh"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> encryptedToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">cookies</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">refreshToken</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">encryptedToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">sendStatus</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">401</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Validate the Origin header to prevent cross-origin refresh attempts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> allowedOrigin </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">ALLOWED_ORIGIN</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"http://localhost:3000"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">origin</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> allowedOrigin</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">sendStatus</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">403</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> token </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">decryptToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">encryptedToken</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> jwt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">verify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">REFRESH_SECRET</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">generateAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// Return expiresIn so the frontend can schedule the next silent refresh</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> accessToken</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">expiresIn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">15</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">60</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">sendStatus</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">403</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div></div><div role="tabpanel" class="tabItem_Ymn6" hidden=""><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">routes/profile.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/profile"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> auth </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">authorization</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">auth</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">sendStatus</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">401</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> token </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">decryptToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">auth</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">split</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">" "</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> user </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> jwt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">verify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">ACCESS_SECRET</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">email</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"dev@example.com"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">sendStatus</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">403</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div></div></div></div></div></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend-implementation">Frontend Implementation<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#frontend-implementation" class="hash-link" aria-label="Direct link to Frontend Implementation" title="Direct link to Frontend Implementation" translate="no">​</a></h2>
<div class="theme-tabs-container tabs-container tabList__CuJ"><ul role="tablist" aria-orientation="horizontal" class="tabs"><li role="tab" tabindex="0" aria-selected="true" class="tabs__item tabItem_LNqP tabs__item--active">JavaScript</li></ul><div class="margin-top--md"><div role="tabpanel" class="tabItem_Ymn6"><p>The access token lives <strong>only in a module-scoped variable</strong>. It is never written to any persistent storage.</p><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="auth-state">Auth State<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#auth-state" class="hash-link" aria-label="Direct link to Auth State" title="Direct link to Auth State" translate="no">​</a></h3><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">auth.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Module-scoped — not accessible outside this file</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> refreshTimer </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="login">Login<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#login" class="hash-link" aria-label="Direct link to Login" title="Direct link to Login" translate="no">​</a></h3><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">auth.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">login</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> res </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"http://localhost:4000/login"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">method</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"POST"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">credentials</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"include"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// sends/receives cookies</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> data </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">accessToken</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Schedule a proactive refresh before the token expires</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">scheduleRefresh</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">expiresIn</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="fetch-wrapper-with-auto-refresh">Fetch Wrapper with Auto-Refresh<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#fetch-wrapper-with-auto-refresh" class="hash-link" aria-label="Direct link to Fetch Wrapper with Auto-Refresh" title="Direct link to Fetch Wrapper with Auto-Refresh" translate="no">​</a></h3><p>This wrapper transparently handles token expiry. If a request gets a <code>401</code> or <code>403</code>, it silently refreshes the access token and retries once.</p><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">auth.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">apiFetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">url</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> options </span><span class="token parameter operator" style="color:#393A34">=</span><span class="token parameter"> </span><span class="token parameter punctuation" style="color:#393A34">{</span><span class="token parameter punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  options</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">headers</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">options</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">headers</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">Authorization</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Bearer </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">accessToken</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> res </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">url</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">options</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">credentials</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"include"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">status</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">401</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">status</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">403</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">refreshAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// try to get a new access token</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    options</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Authorization</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Bearer </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">accessToken</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    res </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">url</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">options</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">credentials</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"include"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="silent-refresh">Silent Refresh<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#silent-refresh" class="hash-link" aria-label="Direct link to Silent Refresh" title="Direct link to Silent Refresh" translate="no">​</a></h3><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">auth.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">refreshAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> res </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"http://localhost:4000/refresh"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">method</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"POST"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">credentials</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"include"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// the httpOnly cookie is sent automatically</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">ok</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Session expired. Please log in again."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> data </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">accessToken</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Reschedule the next proactive refresh</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">scheduleRefresh</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">expiresIn</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div><h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="proactive-refresh-scheduling">Proactive Refresh Scheduling<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#proactive-refresh-scheduling" class="hash-link" aria-label="Direct link to Proactive Refresh Scheduling" title="Direct link to Proactive Refresh Scheduling" translate="no">​</a></h3><p>Since the access token is encrypted, the frontend cannot parse its <code>exp</code> claim directly. Instead, the server returns <code>expiresIn</code> (in seconds) alongside the token, which the frontend uses to schedule a refresh ~1 minute before expiry.</p><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">auth.js</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">scheduleRefresh</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">expiresInSeconds</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">refreshTimer</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">clearTimeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">refreshTimer</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Refresh 60 seconds before expiry to avoid any clock drift</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> delay </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">expiresInSeconds </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">60</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1000</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  refreshTimer </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setTimeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">refreshAccessToken</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// Token could not be refreshed — force the user to log in again</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      accessToken </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> delay</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div><div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Why not parse <code>exp</code> from the token?</div><div class="admonitionContent_BuS1"><p>Because the access token is encrypted, its payload is opaque to the frontend. Using the server-returned <code>expiresIn</code> value is the correct approach here. If you were using plain (non-encrypted) JWTs, you could decode the <code>exp</code> claim directly with <code>JSON.parse(atob(token.split('.')[1]))</code>.</p></div></div></div></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="security-practices">Security Practices<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#security-practices" class="hash-link" aria-label="Direct link to Security Practices" title="Direct link to Security Practices" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="encrypted-jwts">Encrypted JWTs<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#encrypted-jwts" class="hash-link" aria-label="Direct link to Encrypted JWTs" title="Direct link to Encrypted JWTs" translate="no">​</a></h3>
<table><thead><tr><th>Practice</th><th>Reason</th></tr></thead><tbody><tr><td>Encrypt all JWTs leaving the server</td><td>Prevents token content exposure if intercepted or logged</td></tr><tr><td>URL-encode encrypted output</td><td>CryptoJS Base64 contains characters unsafe in headers and cookies</td></tr><tr><td>Decrypt on server before verifying signature</td><td>Maintains integrity check even with encryption</td></tr><tr><td>Keep payloads minimal</td><td>Limits exposure if decryption were ever compromised</td></tr><tr><td>Short-lived access tokens (15m)</td><td>Limits exposure window if a token is stolen</td></tr><tr><td>Combine encryption with signing</td><td>Encryption provides confidentiality; signing provides integrity</td></tr></tbody></table>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Use cryptographically secure algorithms</div><div class="admonitionContent_BuS1"><p>Always use modern, vetted cryptographic algorithms and libraries. Good examples include <strong>AES-256-GCM</strong> for authenticated encryption, <strong>ChaCha20-Poly1305</strong> for AEAD, <strong>Ed25519</strong> for signatures, and <strong>SHA-256/SHA-3</strong> for hashing.</p><p>Also review your crypto choices regularly and verify they have not been deprecated or broken by new research, standards updates, or compliance requirements.</p></div></div>
<div class="theme-admonition theme-admonition-danger admonition_xJq3 alert alert--danger"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>Broken crypto alert</div><div class="admonitionContent_BuS1"><p>Do not use broken or weak algorithms such as <strong>DES</strong>, <strong>RC4</strong>, <strong>MD5</strong>, or <strong>SHA-1</strong> for security-critical use.<br>
<!-- -->Example: <strong>SHA-1</strong> is considered broken for collision resistance and should not be used for signatures or integrity protection in new systems.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cookie-flags">Cookie Flags<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#cookie-flags" class="hash-link" aria-label="Direct link to Cookie Flags" title="Direct link to Cookie Flags" translate="no">​</a></h3>
<table><thead><tr><th>Flag</th><th>Value</th><th>Effect</th></tr></thead><tbody><tr><td><code>httpOnly</code></td><td><code>true</code></td><td>Inaccessible to JavaScript — XSS-proof</td></tr><tr><td><code>secure</code></td><td><code>true</code></td><td>Only sent over HTTPS</td></tr><tr><td><code>sameSite</code></td><td><code>strict</code></td><td>Never sent in cross-site requests — CSRF protection</td></tr><tr><td><code>path</code></td><td><code>/refresh</code></td><td>Scoped to a single endpoint — minimises exposure</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="access-token-vs-bearer-token">Access Token vs Bearer Token<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#access-token-vs-bearer-token" class="hash-link" aria-label="Direct link to Access Token vs Bearer Token" title="Direct link to Access Token vs Bearer Token" translate="no">​</a></h3>
<ul>
<li class=""><strong>Access token</strong> — <em>what</em> the token is for (authorizing API access)</li>
<li class=""><strong>Bearer token</strong> — <em>how</em> the token is used (anyone who holds it can use it)</li>
</ul>
<p>In standard REST APIs, the access token travels as a bearer token in the <code>Authorization</code> header. With encrypted JWTs, the bearer concept still applies — the server decrypts and verifies on each request.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="production-hardening">Production Hardening<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#production-hardening" class="hash-link" aria-label="Direct link to Production Hardening" title="Direct link to Production Hardening" translate="no">​</a></h2>
<div class="theme-admonition theme-admonition-danger admonition_xJq3 alert alert--danger"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>Before you ship</div><div class="admonitionContent_BuS1"><ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <strong>Rotate refresh tokens</strong> on every use (one-time-use tokens)</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <strong>Store refresh tokens server-side</strong> (DB or cache) for revocation support</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <strong>Enforce HTTPS</strong> everywhere — never allow HTTP in production</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <strong>Validate <code>Origin</code> header</strong> on the <code>/refresh</code> endpoint</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <strong>Implement logout</strong> that clears the <code>refreshToken</code> cookie server-side</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <strong>Add a Content Security Policy</strong> header to defend against XSS</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <strong>Load all secrets from environment variables</strong> — never commit them</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <strong>Switch to <code>sameSite: "lax"</code></strong> if you add OAuth/SSO providers</li>
</ul></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="glossary">Glossary<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#glossary" class="hash-link" aria-label="Direct link to Glossary" title="Direct link to Glossary" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="access-token">Access Token<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#access-token" class="hash-link" aria-label="Direct link to Access Token" title="Direct link to Access Token" translate="no">​</a></h3>
<p>A short-lived credential sent with each API request to prove the user is authenticated. Typically valid for 15 minutes.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="bearer-token">Bearer Token<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#bearer-token" class="hash-link" aria-label="Direct link to Bearer Token" title="Direct link to Bearer Token" translate="no">​</a></h3>
<p>A token authentication scheme where any party possessing the token can use it. Access tokens in web apps are bearer tokens by convention.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="refresh-token">Refresh Token<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#refresh-token" class="hash-link" aria-label="Direct link to Refresh Token" title="Direct link to Refresh Token" translate="no">​</a></h3>
<p>A long-lived credential used exclusively to obtain new access tokens, without requiring the user to log in again.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="http-only-cookie">HTTP-only Cookie<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#http-only-cookie" class="hash-link" aria-label="Direct link to HTTP-only Cookie" title="Direct link to HTTP-only Cookie" translate="no">​</a></h3>
<p>A browser cookie that cannot be read or written by JavaScript. Protects the refresh token from XSS-based theft.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="csrf-cross-site-request-forgery">CSRF (Cross-Site Request Forgery)<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#csrf-cross-site-request-forgery" class="hash-link" aria-label="Direct link to CSRF (Cross-Site Request Forgery)" title="Direct link to CSRF (Cross-Site Request Forgery)" translate="no">​</a></h3>
<p>An attack in which a malicious site tricks a user's browser into sending authenticated requests to another site. Mitigated here by <code>sameSite: strict</code> and scoping the refresh cookie to <code>path: /refresh</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="xss-cross-site-scripting">XSS (Cross-Site Scripting)<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#xss-cross-site-scripting" class="hash-link" aria-label="Direct link to XSS (Cross-Site Scripting)" title="Direct link to XSS (Cross-Site Scripting)" translate="no">​</a></h3>
<p>A vulnerability allowing attackers to inject malicious JavaScript into your page. Keeping tokens out of <code>localStorage</code>/<code>sessionStorage</code> means XSS cannot steal them.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="jwt-json-web-token">JWT (JSON Web Token)<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#jwt-json-web-token" class="hash-link" aria-label="Direct link to JWT (JSON Web Token)" title="Direct link to JWT (JSON Web Token)" translate="no">​</a></h3>
<p>A signed token format used to transmit identity and authorization claims between parties.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="encrypted-jwt-custom-envelope">Encrypted JWT (Custom Envelope)<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#encrypted-jwt-custom-envelope" class="hash-link" aria-label="Direct link to Encrypted JWT (Custom Envelope)" title="Direct link to Encrypted JWT (Custom Envelope)" translate="no">​</a></h3>
<p>A JWT whose content is encrypted using AES before transmission, so only the server can read the payload. This guide uses a custom CryptoJS + JWT envelope — not the JWE standard (RFC 7516). For standards-compliant encrypted JWTs, use the <a href="https://github.com/panva/jose" target="_blank" rel="noopener noreferrer" class=""><code>jose</code></a> library.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="samesite-cookie-policy">SameSite Cookie Policy<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#samesite-cookie-policy" class="hash-link" aria-label="Direct link to SameSite Cookie Policy" title="Direct link to SameSite Cookie Policy" translate="no">​</a></h3>
<p>A browser attribute controlling whether cookies are sent in cross-site requests. <code>strict</code> means the cookie is only sent when the request originates from the same site. Note: <code>strict</code> is incompatible with OAuth/SSO redirect flows — use <code>lax</code> in those cases.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="csp-content-security-policy">CSP (Content Security Policy)<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#csp-content-security-policy" class="hash-link" aria-label="Direct link to CSP (Content Security Policy)" title="Direct link to CSP (Content Security Policy)" translate="no">​</a></h3>
<p>An HTTP header that restricts which scripts and resources the browser is permitted to load, reducing the impact of XSS attacks.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://docs.sadeeminfo.com/blog/secure-bearer-token-storage#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>Secure bearer-token handling is not about a single storage choice, but about a full lifecycle: short-lived access tokens in memory, refresh tokens in HTTP-only cookies, strict server validation, and proactive refresh on the client. When combined with HTTPS, origin checks, token rotation, and CSP, this pattern provides a practical and production-ready baseline for modern web authentication.</p>]]></content>
        <author>
            <name>Mohamed El Amine Meghni</name>
        </author>
        <category label="security" term="security"/>
        <category label="authentication" term="authentication"/>
        <category label="jwt" term="jwt"/>
        <category label="tokens" term="tokens"/>
        <category label="backend" term="backend"/>
        <category label="frontend" term="frontend"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Guidelines for Effective Use of Free AI Coding Tools]]></title>
        <id>https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools</id>
        <link href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools"/>
        <updated>2026-02-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[For developers at Sadeem informatique]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>For developers at <strong>Sadeem informatique</strong></p>
</blockquote>
<p>This guide defines best practices for using free-tier AI tools to support development work.<br>
<!-- -->The goal is to improve productivity, learning, and code quality while keeping developers in control of architecture, logic, and security decisions.</p>
<div class="theme-admonition theme-admonition-important admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Source Priority</div><div class="admonitionContent_BuS1"><p>Official framework and programming language documentation are the <strong>primary sources of truth</strong>. AI tools can be useful assistants, but responses may be outdated or incomplete and must be validated before use.</p></div></div>
<table><thead><tr><th>Quick Navigation</th><th></th></tr></thead><tbody><tr><td>Principles</td><td><a class="" href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#1-use-ai-as-a-knowledge-assistant-not-a-code-replacement">1. Knowledge Assistant</a>, <a class="" href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#2-break-requests-into-small-focused-questions">2. Focused Questions</a>, <a class="" href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#3-prioritize-understanding-over-code-generation">3. Understanding First</a></td></tr><tr><td>Execution</td><td><a class="" href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#4-use-ai-to-improve-debugging-strategy">4. Debugging Strategy</a>, <a class="" href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#5-validate-all-ai-output-before-use">5. Validate Output</a>, <a class="" href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#6-combine-ai-advice-with-internal-knowledge">6. Internal Knowledge</a></td></tr><tr><td>Governance</td><td><a class="" href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#7-never-share-sensitive-information">7. Sensitive Data</a>, <a class="" href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#8-use-ai-to-accelerate-learning-not-shortcut-thinking">8. Learning Mindset</a>, <a class="" href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#9-use-ai-for-repetitive-tasks-and-boilerplate">9. Boilerplate Usage</a></td></tr></tbody></table>
<p><img decoding="async" loading="lazy" src="https://images.pexels.com/photos/16094056/pexels-photo-16094056.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=1600" alt="Software engineer using an AI assistant on a laptop" class="img_ev3q"></p>
<p><em>Photo by <a href="https://www.pexels.com/photo/laptop-with-chatgpt-ai-system-16094056/" target="_blank" rel="noopener noreferrer" class="">Matheus Bertelli</a> on Pexels.</em></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-use-ai-as-a-knowledge-assistant-not-a-code-replacement">1. Use AI as a Knowledge Assistant, Not a Code Replacement<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#1-use-ai-as-a-knowledge-assistant-not-a-code-replacement" class="hash-link" aria-label="Direct link to 1. Use AI as a Knowledge Assistant, Not a Code Replacement" title="Direct link to 1. Use AI as a Knowledge Assistant, Not a Code Replacement" translate="no">​</a></h2>
<p>AI tools should primarily help developers understand concepts, not replace engineering decisions.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Core Rule</div><div class="admonitionContent_BuS1"><p>Use AI to <strong>clarify</strong> and <strong>accelerate thinking</strong>, not to outsource ownership.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="appropriate-usage">Appropriate Usage<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#appropriate-usage" class="hash-link" aria-label="Direct link to Appropriate Usage" title="Direct link to Appropriate Usage" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend-react">Frontend (React)<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#frontend-react" class="hash-link" aria-label="Direct link to Frontend (React)" title="Direct link to Frontend (React)" translate="no">​</a></h4>
<ul>
<li class="">Ask AI to explain how React state differs from refs.</li>
<li class="">Request a small example of a controlled form component.</li>
<li class="">Ask why a component is re-rendering unnecessarily.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend-laravel--nestjs">Backend (Laravel / NestJS)<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#backend-laravel--nestjs" class="hash-link" aria-label="Direct link to Backend (Laravel / NestJS)" title="Direct link to Backend (Laravel / NestJS)" translate="no">​</a></h4>
<ul>
<li class="">Ask AI to explain middleware lifecycle.</li>
<li class="">Request clarification on dependency injection in NestJS.</li>
<li class="">Ask how Laravel queues work conceptually.</li>
</ul>
<p>Avoid requesting full modules, features, or production-ready systems from AI tools.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-break-requests-into-small-focused-questions">2. Break Requests Into Small, Focused Questions<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#2-break-requests-into-small-focused-questions" class="hash-link" aria-label="Direct link to 2. Break Requests Into Small, Focused Questions" title="Direct link to 2. Break Requests Into Small, Focused Questions" translate="no">​</a></h2>
<p>Free-tier plans perform best with targeted prompts.</p>
<p>Instead of asking:<br>
<code>Build a full authentication system</code></p>
<p>Prefer asking step-by-step:</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend-react-1">Frontend (React)<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#frontend-react-1" class="hash-link" aria-label="Direct link to Frontend (React)" title="Direct link to Frontend (React)" translate="no">​</a></h4>
<ol>
<li class="">How should I structure authentication context?</li>
<li class="">How do I store tokens securely in React apps?</li>
<li class="">Why is my protected route redirect looping?</li>
</ol>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend-laravel">Backend (Laravel)<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#backend-laravel" class="hash-link" aria-label="Direct link to Backend (Laravel)" title="Direct link to Backend (Laravel)" translate="no">​</a></h4>
<ol>
<li class="">Explain how Laravel guards differ from middleware.</li>
<li class="">How can I structure authentication services cleanly?</li>
</ol>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend-nestjs">Backend (NestJS)<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#backend-nestjs" class="hash-link" aria-label="Direct link to Backend (NestJS)" title="Direct link to Backend (NestJS)" translate="no">​</a></h4>
<ol>
<li class="">How do guards interact with interceptors?</li>
<li class="">What is the recommended folder structure for auth modules?</li>
</ol>
<p>This approach produces clearer answers and reduces wasted usage.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Bad prompt  -&gt; "Build full auth"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Good prompt -&gt; "Explain auth context boundaries in React"</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-prioritize-understanding-over-code-generation">3. Prioritize Understanding Over Code Generation<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#3-prioritize-understanding-over-code-generation" class="hash-link" aria-label="Direct link to 3. Prioritize Understanding Over Code Generation" title="Direct link to 3. Prioritize Understanding Over Code Generation" translate="no">​</a></h2>
<p>Developers should use AI to explain logic and review implementation choices.</p>
<p>Examples:</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="react">React<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#react" class="hash-link" aria-label="Direct link to React" title="Direct link to React" translate="no">​</a></h4>
<p>Ask AI to review a component and explain:</p>
<ul>
<li class="">Why a hook dependency warning appears.</li>
<li class="">Whether memoization is useful in this case.</li>
<li class="">If state should be lifted or localized.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="laravel">Laravel<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#laravel" class="hash-link" aria-label="Direct link to Laravel" title="Direct link to Laravel" translate="no">​</a></h4>
<p>Ask AI to explain:</p>
<ul>
<li class="">Why a query might cause N+1 problems.</li>
<li class="">Whether eager loading is appropriate.</li>
<li class="">If a service class improves maintainability.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="nestjs">NestJS<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#nestjs" class="hash-link" aria-label="Direct link to NestJS" title="Direct link to NestJS" translate="no">​</a></h4>
<p>Ask AI to evaluate:</p>
<ul>
<li class="">Whether a provider should be singleton or request-scoped.</li>
<li class="">If a controller contains too much business logic.</li>
</ul>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Practical Lens</div><div class="admonitionContent_BuS1"><p>If you cannot explain <em>why</em> the generated code is correct, it is not ready to merge.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-use-ai-to-improve-debugging-strategy">4. Use AI to Improve Debugging Strategy<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#4-use-ai-to-improve-debugging-strategy" class="hash-link" aria-label="Direct link to 4. Use AI to Improve Debugging Strategy" title="Direct link to 4. Use AI to Improve Debugging Strategy" translate="no">​</a></h2>
<p>AI should guide investigation, not just provide fixes.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="example-prompts">Example Prompts<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#example-prompts" class="hash-link" aria-label="Direct link to Example Prompts" title="Direct link to Example Prompts" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="react-1">React<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#react-1" class="hash-link" aria-label="Direct link to React" title="Direct link to React" translate="no">​</a></h4>
<ul>
<li class="">How do I systematically debug a component that renders twice?</li>
<li class="">What tools should I use to inspect context updates?</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="laravel-1">Laravel<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#laravel-1" class="hash-link" aria-label="Direct link to Laravel" title="Direct link to Laravel" translate="no">​</a></h4>
<ul>
<li class="">How do I trace a failed job through logs and queues?</li>
<li class="">What steps should I follow to debug a slow query?</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="nestjs-1">NestJS<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#nestjs-1" class="hash-link" aria-label="Direct link to NestJS" title="Direct link to NestJS" translate="no">​</a></h4>
<ul>
<li class="">How do I trace a request lifecycle across modules?</li>
<li class="">Where should I log to debug injection failures?</li>
</ul>
<p>This strengthens developer problem-solving skills across the team.</p>
<details class="details_lb9f alert alert--info details_b_Ee" data-collapsed="true"><summary><strong>Debugging prompt template</strong></summary><div><div class="collapsibleContent_i85q"><div class="language-md codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-md codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Context:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Stack: React/Laravel/NestJS</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Symptom:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Expected behavior:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> What I already tried:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Logs / errors:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Question:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">"Give me a step-by-step investigation path, not just a fix."</span><br></span></code></pre></div></div></div></div></details>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-validate-all-ai-output-before-use">5. Validate All AI Output Before Use<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#5-validate-all-ai-output-before-use" class="hash-link" aria-label="Direct link to 5. Validate All AI Output Before Use" title="Direct link to 5. Validate All AI Output Before Use" translate="no">​</a></h2>
<p>Any AI-generated suggestion must be:</p>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Reviewed by the developer</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Tested locally</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Checked against company coding standards</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Evaluated for security implications</li>
</ul>
<p>Example: if AI suggests storing tokens in localStorage in a React app, developers must verify whether this aligns with company security policy.</p>
<div class="theme-admonition theme-admonition-danger admonition_xJq3 alert alert--danger"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>Mandatory Gate</div><div class="admonitionContent_BuS1"><p>No AI-generated change moves forward without human review and local verification.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-combine-ai-advice-with-internal-knowledge">6. Combine AI Advice With Internal Knowledge<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#6-combine-ai-advice-with-internal-knowledge" class="hash-link" aria-label="Direct link to 6. Combine AI Advice With Internal Knowledge" title="Direct link to 6. Combine AI Advice With Internal Knowledge" translate="no">​</a></h2>
<p>AI responses must be validated against:</p>
<ul>
<li class="">Official framework documentation (primary source).</li>
<li class="">Official framework and programming language documentation (primary sources).</li>
<li class="">Existing company codebases.</li>
<li class="">Internal architecture standards.</li>
</ul>
<p>Example: if AI proposes a new service pattern in Laravel, confirm it matches the company's existing service and repository structure before adopting it.</p>
<table><thead><tr><th>Source</th><th>Why it matters</th></tr></thead><tbody><tr><td>Official docs</td><td>Correctness and current framework behavior</td></tr><tr><td>Company codebase</td><td>Consistency with existing patterns</td></tr><tr><td>Internal standards</td><td>Maintainability, governance, and quality</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-never-share-sensitive-information">7. Never Share Sensitive Information<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#7-never-share-sensitive-information" class="hash-link" aria-label="Direct link to 7. Never Share Sensitive Information" title="Direct link to 7. Never Share Sensitive Information" translate="no">​</a></h2>
<p>When using public AI tools:</p>
<ul>
<li class="">Do not paste client data.</li>
<li class="">Do not paste credentials or API keys.</li>
<li class="">Avoid sharing proprietary business logic.</li>
<li class="">Use anonymized examples when possible.</li>
</ul>
<p>This protects both company and client assets.</p>
<div class="language-diff codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-diff codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">+ Do this: "User A cannot access report endpoint (403)."</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">- Not this: "Client X with account #29431 and token abc123... cannot access..."</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-use-ai-to-accelerate-learning-not-shortcut-thinking">8. Use AI to Accelerate Learning, Not Shortcut Thinking<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#8-use-ai-to-accelerate-learning-not-shortcut-thinking" class="hash-link" aria-label="Direct link to 8. Use AI to Accelerate Learning, Not Shortcut Thinking" title="Direct link to 8. Use AI to Accelerate Learning, Not Shortcut Thinking" translate="no">​</a></h2>
<p>Recommended learning uses:</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend-react-2">Frontend (React)<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#frontend-react-2" class="hash-link" aria-label="Direct link to Frontend (React)" title="Direct link to Frontend (React)" translate="no">​</a></h4>
<ul>
<li class="">Understanding performance optimization strategies.</li>
<li class="">Learning accessibility best practices.</li>
<li class="">Reviewing component design patterns.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend-laravel--nestjs-1">Backend (Laravel / NestJS)<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#backend-laravel--nestjs-1" class="hash-link" aria-label="Direct link to Backend (Laravel / NestJS)" title="Direct link to Backend (Laravel / NestJS)" translate="no">​</a></h4>
<ul>
<li class="">Exploring clean architecture patterns.</li>
<li class="">Understanding caching strategies.</li>
<li class="">Learning message queue patterns.</li>
</ul>
<p>AI should help developers grow their expertise, not bypass it.</p>
<blockquote>
<p>Teams that treat AI as a tutor improve faster than teams that treat AI as an autopilot.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="9-use-ai-for-repetitive-tasks-and-boilerplate">9. Use AI for Repetitive Tasks and Boilerplate<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#9-use-ai-for-repetitive-tasks-and-boilerplate" class="hash-link" aria-label="Direct link to 9. Use AI for Repetitive Tasks and Boilerplate" title="Direct link to 9. Use AI for Repetitive Tasks and Boilerplate" translate="no">​</a></h2>
<p>Free AI tools are ideal for reducing time spent on repetitive or boilerplate tasks. This helps speed development and reduces human error.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="frontend-react-3">Frontend (React)<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#frontend-react-3" class="hash-link" aria-label="Direct link to Frontend (React)" title="Direct link to Frontend (React)" translate="no">​</a></h4>
<ul>
<li class="">Generating boilerplate form validation code.</li>
<li class="">Translating static content or labels for multiple languages.</li>
<li class="">Creating repetitive UI components like tables, cards, or modals.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="backend-laravel--nestjs-2">Backend (Laravel / NestJS)<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#backend-laravel--nestjs-2" class="hash-link" aria-label="Direct link to Backend (Laravel / NestJS)" title="Direct link to Backend (Laravel / NestJS)" translate="no">​</a></h4>
<ul>
<li class="">Generating boilerplate CRUD endpoints.</li>
<li class="">Producing standard DTOs or request validation classes.</li>
<li class="">Automating repetitive configuration or migration templates.</li>
</ul>
<p>Developers should still review AI-generated boilerplate to ensure it aligns with internal coding standards and project architecture.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="safe-division-of-responsibility">Safe Division of Responsibility<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#safe-division-of-responsibility" class="hash-link" aria-label="Direct link to Safe Division of Responsibility" title="Direct link to Safe Division of Responsibility" translate="no">​</a></h3>
<table><thead><tr><th>AI can draft</th><th>Developers must decide</th></tr></thead><tbody><tr><td>Repetitive scaffolding</td><td>Architecture and boundaries</td></tr><tr><td>Prompted examples</td><td>Security controls</td></tr><tr><td>First-pass refactors</td><td>Final behavior and test coverage</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://docs.sadeeminfo.com/blog/effective-use-of-free-ai-coding-tools#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>Free-tier AI tools can significantly enhance developer effectiveness when used thoughtfully.<br>
<!-- -->At Sadeem informatique, the goal is to use these tools to support learning, reinforce engineering standards, reduce repetitive effort, and improve code quality, not to replace developer judgment.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Provider Rotation</div><div class="admonitionContent_BuS1"><p>Rotate between multiple AI providers (for example: ChatGPT, Claude, and Grok) when needed to compare answers, reduce free-tier limit impact, and avoid single-provider blind spots.</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>Final Checklist</div><div class="admonitionContent_BuS1"><ul>
<li class="">Keep prompts focused</li>
<li class="">Verify every output</li>
<li class="">Protect sensitive data</li>
<li class="">Preserve engineering ownership</li>
</ul></div></div>]]></content>
        <author>
            <name>Mohamed El Amine Meghni</name>
        </author>
        <category label="ai" term="ai"/>
        <category label="development" term="development"/>
        <category label="react" term="react"/>
        <category label="laravel" term="laravel"/>
        <category label="nestjs" term="nestjs"/>
    </entry>
</feed>