<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hemant Sharma</title>
    <description>The latest articles on DEV Community by Hemant Sharma (@hemants1703).</description>
    <link>https://dev.to/hemants1703</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3650216%2F27046785-971c-4204-938d-2659432e4572.jpg</url>
      <title>DEV Community: Hemant Sharma</title>
      <link>https://dev.to/hemants1703</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hemants1703"/>
    <language>en</language>
    <item>
      <title>Building a Production-Grade CI/CD Pipeline with GitHub Actions, GHCR, Docker, and a VPS</title>
      <dc:creator>Hemant Sharma</dc:creator>
      <pubDate>Mon, 10 Aug 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/hemants1703/building-a-production-grade-cicd-pipeline-with-github-actions-ghcr-docker-and-a-vps-1g1e</link>
      <guid>https://dev.to/hemants1703/building-a-production-grade-cicd-pipeline-with-github-actions-ghcr-docker-and-a-vps-1g1e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0xp102v0nmo3vqy4cgc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0xp102v0nmo3vqy4cgc6.png" alt="Building a Production-Grade CI/CD Pipeline with GitHub Actions, GHCR, Docker, and a VPS" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I had a NestJS backend that needed to go live, and I did not want the deploy process to be me SSHing into the VPS every time. I also did not want to outsource the whole thing to a PaaS "Deploy" button. I wanted a proper pipeline, so I can simply push code, run tests, ship a container, cut traffic over safely, and deploy the latest version.&lt;/p&gt;

&lt;p&gt;So I built one. GitHub Actions, GHCR, Docker, nginx, and a DigitalOcean VPS. This post is about that story — what I set up, what I got wrong along the way, and what still matters after the host changed later.&lt;/p&gt;

&lt;p&gt;The backend sat in a pnpm monorepo next to a Next.js frontend. Vercel took care of the frontend. Postgres lived on a managed provider. The part I spent real time on was getting the API from &lt;code&gt;git push&lt;/code&gt; to a healthy process behind TLS.&lt;/p&gt;

&lt;p&gt;Throughout the blog, I will be referring to the project codebase, to refer the codebase you can &lt;a href="https://github.com/hemants1703/ambitiousyou" rel="noopener noreferrer"&gt;click here&lt;/a&gt; to see the full repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I needed from the pipeline
&lt;/h2&gt;

&lt;p&gt;Before I touched the YAML files, I wrote down what "good" looked like to me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A push to the right branch should deploy. I should not be copy-pasting commands on the server for normal releases. Rather, it should be automated.&lt;/li&gt;
&lt;li&gt;The same commit should produce the same image, so rollback is "run an older SHA", not "try to rebuild what we think we shipped."&lt;/li&gt;
&lt;li&gt;If the new version is broken, the old one should keep serving.&lt;/li&gt;
&lt;li&gt;Database Migrations should happen before the new backend business logic takes traffic.&lt;/li&gt;
&lt;li&gt;Cost should stay low — I had DigitalOcean credits, so a small VPS was fine.&lt;/li&gt;
&lt;li&gt;Dev and prod should be separate environments, but the same pipeline shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A managed platform would have been less work. I wanted the loop in my own hands instead: build, registry, host, cutover.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a release works
&lt;/h2&gt;

&lt;p&gt;Here is the flow I had in plain terms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push (main | dev branch on GitHub)
        │
        ▼
   GitHub Actions
   ┌──────────────────────────────┐
   │ 1. pnpm test (backend)       │
   │ 2. verify DB connectivity    │
   │ 3. drizzle-kit migrate       │
   │ 4. build → push GHCR (:sha)  │
   │ 5. scp deploy.sh             │
   │ 6. SSH → blue-green swap     │
   └──────────────────────────────┘
        │
        ▼
   VPS (nginx + Let's Encrypt SSL)
   idle port → health gate → upstream rewrite → SIGTERM drain

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the same pipeline as a full diagram — Actions through GHCR into blue-green on the VPS:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7nhvu4x044s8n84s7nc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7nhvu4x044s8n84s7nc3.png" alt="Backend CI/CD pipeline: GitHub Actions, GHCR, Docker, and zero-downtime blue-green on a VPS" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few rules I treated as non-negotiable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If tests fail → nothing gets built, migrated, or deployed at all.&lt;/li&gt;
&lt;li&gt;Database connectivity is checked, and only then are the migrations run, &lt;em&gt;before&lt;/em&gt; containers swap.&lt;/li&gt;
&lt;li&gt;The thing we deploy is an image tagged with the git SHA — not a vague &lt;code&gt;:latest&lt;/code&gt; tag to ensure uniqueness of the image.&lt;/li&gt;
&lt;li&gt;The new container starts on a free port and has to pass a DB-aware &lt;code&gt;/health&lt;/code&gt; check through the exposed API route.&lt;/li&gt;
&lt;li&gt;If that check fails, we simply abort moving to the latest image container. The previous container stays alive and handles the traffic as usual.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The API container only listened on localhost within the VPS, and nginx terminated TLS and was the only thing exposed to the internet as what a reverse proxy should be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the image (and not lying to Docker)
&lt;/h2&gt;

&lt;p&gt;I build a multi-stage image from the &lt;strong&gt;repo root&lt;/strong&gt; , not from &lt;code&gt;apps/backend&lt;/code&gt; alone. In a pnpm workspace the backend depends on a shared package. If the Docker context ignores that, you get a build that looks fine until it is not.&lt;/p&gt;

&lt;p&gt;The Dockerfile installs with &lt;code&gt;--filter backend...&lt;/code&gt;, builds &lt;code&gt;packages/shared&lt;/code&gt; then &lt;code&gt;apps/backend&lt;/code&gt;, and uses &lt;code&gt;pnpm deploy --prod&lt;/code&gt; so the runtime stage is a pruned tree instead of the whole monorepo.&lt;/p&gt;

&lt;p&gt;A few decisions that saved me pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;bookworm-slim (glibc), not Alpine.&lt;/strong&gt; bcrypt and musl have bitten enough people. I picked the boring base.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run as &lt;code&gt;node&lt;/code&gt;, not root.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;HEALTHCHECK&lt;/code&gt; hits &lt;code&gt;/health&lt;/code&gt;.&lt;/strong&gt;"Process is up" is not the same as "can talk to Postgres."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exec-form &lt;code&gt;CMD&lt;/code&gt;.&lt;/strong&gt; Node is PID 1, so &lt;code&gt;SIGTERM&lt;/code&gt; reaches Nest's shutdown hooks and the pool can drain cleanly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-bookworm-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production PORT=3001&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build --chown=node:node /out ./&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3001&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=5s --start-period=20s --retries=3 \&lt;/span&gt;
  CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3001)+'/health').then(r=&amp;gt;process.exit(r.ok?0:1)).catch(()=&amp;gt;process.exit(1))"

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/main"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Images go to GHCR as &lt;code&gt;&amp;lt;image&amp;gt;:&amp;lt;git-sha&amp;gt;&lt;/code&gt;, with an env &lt;code&gt;*-latest&lt;/code&gt; tag only as a convenience. The deploy script always gets the SHA. Buildx cache (&lt;code&gt;type=gha&lt;/code&gt;) keeps rebuilds from starting from zero every push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrate first, then cut traffic
&lt;/h2&gt;

&lt;p&gt;This one is easy to get backwards.&lt;/p&gt;

&lt;p&gt;If you swap the container and &lt;em&gt;then&lt;/em&gt; migrate, the new code can boot against an old schema and fall over in front of users. If you migrate first (and keep schema changes expand/contract-friendly), the old container keeps serving while the database moves forward. Then the new container takes over.&lt;/p&gt;

&lt;p&gt;"Migration failed" often means "we never connected." &lt;code&gt;drizzle-kit&lt;/code&gt; can sit behind a spinner for a long time without telling you much. So the workflow prints &lt;code&gt;host/port/user&lt;/code&gt; (password stays out of the logs) and runs a quick &lt;code&gt;select 1&lt;/code&gt; with a 10s timeout before migrate.&lt;/p&gt;

&lt;p&gt;On GitHub-hosted runners, some managed Postgres hosts prefer IPv6 so, forcing &lt;code&gt;NODE_OPTIONS=--dns-result-order=ipv4first&lt;/code&gt; helped clear a set of hangs that looked like flaky CI and were really DNS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify DB connectivity&lt;/span&gt;
  &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/backend&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DATABASE_URL }}&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;# Log host/port/user (never the password), then:&lt;/span&gt;
    &lt;span class="s"&gt;# new Client({ connectionString, connectionTimeoutMillis: 10000 }).query('select 1')&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run DB migrations&lt;/span&gt;
  &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/backend&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DATABASE_URL }}&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pnpm exec drizzle-kit migrate&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Blue-green on the VPS
&lt;/h2&gt;

&lt;p&gt;I did not want a &lt;code&gt;502&lt;/code&gt; window where the old process is gone, and the new one is still starting creating a time space where user requests might hit a wall, and they simply see a broken application. So each environment has two ports (prod &lt;code&gt;3001&lt;/code&gt; / &lt;code&gt;3002&lt;/code&gt;, dev &lt;code&gt;3101&lt;/code&gt; / &lt;code&gt;3102&lt;/code&gt;). The new container always starts on the idle one.&lt;/p&gt;

&lt;p&gt;Only after &lt;code&gt;/health&lt;/code&gt; succeeds do we rewrite nginx's upstream and reload. Then we stop the old container with a 30s grace period so in-flight work can finish.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;infra/deploy.sh&lt;/code&gt; lives in the repo and gets &lt;code&gt;scp&lt;/code&gt;'d on every deployment, so the script on the VPS cannot silently drift from git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Health gate: up to ~60s for the new container to report DB-ready.&lt;/span&gt;
&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;for &lt;/span&gt;_ &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 30&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:&lt;/span&gt;&lt;span class="nv"&gt;$IDLE&lt;/span&gt;&lt;span class="s2"&gt;/health"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;2
&lt;span class="k"&gt;done
if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; 1]&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"!! health check FAILED — aborting, leaving &lt;/span&gt;&lt;span class="nv"&gt;$OLD&lt;/span&gt;&lt;span class="s2"&gt; live"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 80 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
  &lt;/span&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
  exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"upstream backend_&lt;/span&gt;&lt;span class="nv"&gt;$ENV&lt;/span&gt;&lt;span class="s2"&gt; { server 127.0.0.1:&lt;/span&gt;&lt;span class="nv"&gt;$IDLE&lt;/span&gt;&lt;span class="s2"&gt;; }"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="s2"&gt;"/etc/nginx/conf.d/upstream-&lt;/span&gt;&lt;span class="nv"&gt;$ENV&lt;/span&gt;&lt;span class="s2"&gt;.conf"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Containers bind to &lt;code&gt;127.0.0.1&lt;/code&gt; (localhost) only. nginx handles HTTPS with Let's Encrypt as the reverse proxy.&lt;/p&gt;

&lt;p&gt;One small debugging habit that paid off: the SSH step prints &lt;code&gt;hostname&lt;/code&gt;, &lt;code&gt;whoami&lt;/code&gt;, and a directory listing first. If a secret points at the wrong machine, you see it immediately instead of "deploy succeeded" on a host you did not mean to touch.&lt;/p&gt;

&lt;p&gt;The alternative I refused was the classic pull → stop → start on a single port. That leaves a gap where nothing healthy is listening, and there is no automatic "keep the old one."&lt;/p&gt;

&lt;h2&gt;
  
  
  The host itself
&lt;/h2&gt;

&lt;p&gt;I provisioned the VPS with an idempotent &lt;code&gt;infra/setup-vps.sh&lt;/code&gt;: Docker, nginx, Certbot, ufw (22/80/443), fail2ban, swap, key-only SSH, and a deploy user whose sudo is limited to reloading nginx.&lt;/p&gt;

&lt;p&gt;Secrets stay on the host in env files (&lt;code&gt;/opt/&amp;lt;app&amp;gt;/&amp;lt;env&amp;gt;/backend.env&lt;/code&gt;) and get passed in with &lt;code&gt;--env-file&lt;/code&gt;. They do not get baked into the image, and they do not get committed.&lt;/p&gt;

&lt;p&gt;More scripting up front than clicking through a panel. Everything about the host is in git and can be re-run.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few CI details that mattered in a monorepo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;There were path filters on &lt;code&gt;apps/backend/ **&lt;/code&gt;, &lt;code&gt;packages/shared/**&lt;/code&gt;, and the workflow file — so, a frontend-only change should not redeploy the backend or vice versa.&lt;/li&gt;
&lt;li&gt;GitHub Environments were set for both Production vs. Development, so secrets and rules differ by branch without copying the whole workflow.&lt;/li&gt;
&lt;li&gt;Concurrency per branch with &lt;code&gt;cancel-in-progress: false&lt;/code&gt; — so overlapping deploys to the same env should wait, not interleave mid-swap.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packages: write&lt;/code&gt; so &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; can push to GHCR without a long-lived PAT for the normal path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;workflow_dispatch&lt;/code&gt; for the days when I want to ship by hand without relying on a push trigger.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I considered instead
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Why it was tempting&lt;/th&gt;
&lt;th&gt;Why I did not start there&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manual SSH + Compose&lt;/td&gt;
&lt;td&gt;Fast on day one&lt;/td&gt;
&lt;td&gt;Easy to drift, easy to mess up prod, no clean audit trail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build on the VPS from git&lt;/td&gt;
&lt;td&gt;Simple to picture&lt;/td&gt;
&lt;td&gt;Ties the host to your build toolchain; weaker "same commit = same backend" story&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PaaS (e.g. Render) from day one&lt;/td&gt;
&lt;td&gt;Less nginx and SSH&lt;/td&gt;
&lt;td&gt;Less control over cutover and health gating&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Something like Coolify&lt;/td&gt;
&lt;td&gt;Quick UI setup&lt;/td&gt;
&lt;td&gt;It would consume more VPS resources just for one single project, which I mostly wouldn't need since deployment is fully automated through GHA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I wanted those rules sitting in the repo where I could read them.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the host changed
&lt;/h2&gt;

&lt;p&gt;Later the live backend moved off the always-on VPS to a cheaper managed host. The important part: I did not have to redesign the release model. The GHA workflow is still there and can still builds the image, GHCR still can stores it by SHA, migrations still run before the new revision serves traffic. The VPS workflow is still in the repo if I want self-hosting again.&lt;/p&gt;

&lt;p&gt;That was the part I was happiest about. The backend travels. The host is replaceable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If I do this again, I am keeping the same core:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Images tagged by commit SHA&lt;/li&gt;
&lt;li&gt;Migrate before cutover, with a real connectivity check&lt;/li&gt;
&lt;li&gt;Health-gated traffic flip, abort if unhealthy&lt;/li&gt;
&lt;li&gt;Secrets on the runtime side, never in the image&lt;/li&gt;
&lt;li&gt;Path-filtered CI so the monorepo does not waste deploy runs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the pipeline I built and ran. It shipped for &lt;a href="https://dev.to/projects/ambitiousyou"&gt;AmbitiousYou&lt;/a&gt; — workflows and host scripts are under &lt;a href="https://github.com/hemants1703/AmbitiousYou/blob/main/.github/workflows/deploy-backend-to-vps.yml" rel="noopener noreferrer"&gt;&lt;code&gt;.github/workflows/&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;infra/&lt;/code&gt; if you want the full files.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://hemantsharma.tech/blog/7-building-production-grade-cicd-pipeline" rel="noopener noreferrer"&gt;https://hemantsharma.tech/blog/7-building-production-grade-cicd-pipeline&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>ghcr</category>
      <category>docker</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Stop Breaking Your Forms: Type-Safe Server Actions in Next.js</title>
      <dc:creator>Hemant Sharma</dc:creator>
      <pubDate>Mon, 15 Dec 2025 16:43:11 +0000</pubDate>
      <link>https://dev.to/hemants1703/stop-breaking-your-forms-type-safe-server-actions-in-nextjs-4fc3</link>
      <guid>https://dev.to/hemants1703/stop-breaking-your-forms-type-safe-server-actions-in-nextjs-4fc3</guid>
      <description>&lt;p&gt;Build Next.js forms you can't break. Learn the pattern I use to create type-safe Server Actions that catch errors at compile-time, not in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem: Inefficient Forms
&lt;/h2&gt;

&lt;p&gt;I have been building forms in applications for years at this point and after building so many forms, I found out some real issues that always stand in place if you want to scale your application because then you would need real strict conventions for your entire stack. When I was building one of my side projects, I was simply building the form just how the docs or any other blogpost suggested on the internet but sooner as my project grew in size, I found several difficulties maintaining the form and the engineer in me thought it wasn’t quite OK to be honest. The form gave me no confidence in pushing it to production as there were multiple return statements in places then too many if-else blocks in my client component it just created cognitive load and didn’t quite work well for complex forms handling multiple data types and different situations and that was when I decided I need to find a proper solution to this, I dug deeper over the internet and docs and questioning LLMs everywhere I found almost the same solution that I was already implementing that’s when I decided to think through forms myself and I came upon this really good solution myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution: Stateful Contracts
&lt;/h2&gt;

&lt;p&gt;Let’s think of forms in the aspect of “&lt;strong&gt;States&lt;/strong&gt;“ (like states of matter or even state in React if you would). States are an analogy we can apply on forms as well. Any form can have states, for e.g. a form with just 2 fields has a total of 3 states for it’s fields - none filled, 1 filled, all filled. So that means our forms can be in any state and in order to not flush out the input values from the user, we always try to maintain the input data. So in order to maintain the state of our forms we are going to be using the &lt;strong&gt;useActionState()&lt;/strong&gt; hook from React.&lt;/p&gt;

&lt;p&gt;Next up, Types!&lt;/p&gt;

&lt;p&gt;Types are a really really great way of reducing runtime bugs or even catch bugs in comilation of your code before it hits production ensuring you have a really great developer experience and boosted confidence in your code in production, eventually reducing the 3 AM debuggings you had to do 😂&lt;/p&gt;

&lt;p&gt;I have not seen much solutions where developers apply types to React hooks but for a matter of fact and saviour, these hooks do have Types support! I don’t understand why we don’t use types as often, it might increase development time a little bit, but at least after that you have a peace of mind about your code not breaking in production at least without any reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s see the solution in Action!
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Complete code to copy/refer has been provided at the end of this explanation section&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We will create a form where I expect the user to fill in &lt;strong&gt;Name&lt;/strong&gt;, &lt;strong&gt;Email&lt;/strong&gt;, &lt;strong&gt;Age&lt;/strong&gt;. So we have data types of &lt;strong&gt;string&lt;/strong&gt; and &lt;strong&gt;number&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Since our forms are states and would be using &lt;strong&gt;useActionState&lt;/strong&gt; hook consider extracting away the form from your server component since we will need to make the form a client component, we first create a &lt;strong&gt;TypeScript Interface&lt;/strong&gt; that looks something like this&lt;/p&gt;

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

&lt;p&gt;remember to &lt;strong&gt;export&lt;/strong&gt; the interface since we are going to need it in our server actions as well since this is what is our &lt;strong&gt;Stateful Contract&lt;/strong&gt; that will be communicated between the form and the server action.&lt;/p&gt;

&lt;p&gt;After this we create a new file where we describe our server action and this is how thing look initially&lt;/p&gt;

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

&lt;p&gt;Let’s begin by adding the &lt;strong&gt;useActionState&lt;/strong&gt; hook for the form&lt;/p&gt;

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

&lt;p&gt;as of this, let’s ignore any warnings and errors since we are creating a fully typed form and server action contract this is what’s expected, the TypeScript compiler will definitely throw error at you that is what’s going to force you to write a really good and type-safe code.&lt;/p&gt;

&lt;p&gt;I have udpated the server action to be inferring the same types, for the sake of this blog, I won’t be performing any network calls like submitting or anything since that is not our concern in this case&lt;/p&gt;

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

&lt;p&gt;Notice how I have wrapped the age in &lt;strong&gt;Number&lt;/strong&gt; because I have inferred the return type as the contract interface that we defined initially which forced me to make that string value a number since that is what we expect to be.&lt;/p&gt;

&lt;p&gt;Now I update the component and add types to my &lt;strong&gt;useActionState&lt;/strong&gt; hook to ensure a proper contract between the form and the server action.&lt;/p&gt;

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

&lt;p&gt;Now that we have completed the solution to the problem, let’s handle the formState using useEffect or you can even handle it in your TSX if you want.&lt;/p&gt;

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

&lt;p&gt;This way we are going to ensure that we don’t exchange any data among the form and the server action that is not present in the interface that we created initially.&lt;/p&gt;

&lt;p&gt;I also updated the Server Actions and added some basic validation&lt;/p&gt;

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

&lt;p&gt;This practice of adding type to the &lt;strong&gt;useActionState&lt;/strong&gt; hook and the &lt;strong&gt;Server Action&lt;/strong&gt; return type, TypeScript forces us to make sure the exchanging the data is not hindering the contract and ensure seamless two-way data binding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now if you want to add any new field in the form, you will be prompted through an error in both the places in Server Action and the React form that tells you to add it to the contract lowering your chances of breaking anything in production.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Code
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;page.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";

import { useActionState, useEffect, useState } from "react";
import { submitForm } from "./action";

export interface FormState {
  name: string;
  email: string;
  age: number;

  error?: string;
}

export default function Home() {
  const [formValues, setFormValues] = useState&amp;lt;FormState&amp;gt;({
    name: "",
    email: "",
    age: 0,
  });
  const [formState, formAction, isFormPending] = useActionState&amp;lt;FormState, FormData&amp;gt;(submitForm, formValues);

  useEffect(() =&amp;gt; {
    if (formState.error) {
      alert(formState.error);
    }
  }, [formState.error]);

  return (
    &amp;lt;form action={formAction} className="flex flex-col gap-4 max-w-sm mx-auto py-10"&amp;gt;
      &amp;lt;label htmlFor="name"&amp;gt;Name&amp;lt;/label&amp;gt;
      &amp;lt;input
        type="text"
        name="name"
        id="name"
        value={formValues.name}
        onChange={(e) =&amp;gt; setFormValues({ ...formValues, name: e.target.value })}
      /&amp;gt;
      &amp;lt;label htmlFor="email"&amp;gt;Email&amp;lt;/label&amp;gt;
      &amp;lt;input
        type="email"
        name="email"
        id="email"
        value={formValues.email}
        onChange={(e) =&amp;gt; setFormValues({ ...formValues, email: e.target.value })}
      /&amp;gt;
      &amp;lt;label htmlFor="age"&amp;gt;Age&amp;lt;/label&amp;gt;
      &amp;lt;input
        type="number"
        name="age"
        id="age"
        value={formValues.age}
        onChange={(e) =&amp;gt; setFormValues({ ...formValues, age: Number(e.target.value) })}
      /&amp;gt;

      &amp;lt;button type="submit"&amp;gt;{isFormPending ? "Submitting..." : "Submit"}&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;action.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use server";

import { FormState } from "./page";

export async function submitForm(previousState: FormState, formData: FormData): Promise&amp;lt;FormState&amp;gt; {
  const submittedFormValues = {
    name: formData.get("name") as string,
    email: formData.get("email") as string,
    age: Number(formData.get("age") as string),
  };

  console.log("submittedFormValues", submittedFormValues);

  if (submittedFormValues.name === "") {
    return {
      ...previousState,
      error: "Name is required",
    };
  }

  if (submittedFormValues.email === "") {
    return {
      ...previousState,
      error: "Email is required",
    };
  }

  if (submittedFormValues.age === 0 || submittedFormValues.age &amp;lt; 0) {
    return {
      ...previousState,
      error: "Age is required and must be greater than 0",
    };
  }

  return submittedFormValues;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This way we can handle the errors and the data gracefully while still maintaing the Form State&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Page Visibility API</title>
      <dc:creator>Hemant Sharma</dc:creator>
      <pubDate>Mon, 28 Oct 2024 12:00:00 +0000</pubDate>
      <link>https://dev.to/hemants1703/page-visibility-api-3be5</link>
      <guid>https://dev.to/hemants1703/page-visibility-api-3be5</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;The Page Visibility API provides events you can watch for to know when a webpage is visible or in focus or not. This can be useful for saving resources or improving user experience when the user is not actively interacting with the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use Page Visibility API
&lt;/h2&gt;

&lt;p&gt;The Page Visibility API provides two events that you can listen for: &lt;code&gt;visibilitychange&lt;/code&gt; and &lt;code&gt;fullscreenchange&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is an example of how to use the &lt;code&gt;visibilitychange&lt;/code&gt; event to detect when the page is hidden or visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;visibilitychange&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page is hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page is visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use the &lt;code&gt;document.hidden&lt;/code&gt; property to check if the page is currently hidden or visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page is hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page is visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's see it in action
&lt;/h2&gt;

&lt;p&gt;To see the Page Visibility API in action, let's create a simple web page that logs the visibility state of the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Page Visibility API Example&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;visibilitychange&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page is hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page is visible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we are logging the visibility state of the page whenever the &lt;code&gt;visibilitychange&lt;/code&gt; event is triggered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Applications
&lt;/h2&gt;

&lt;p&gt;The Page Visibility API can be incredibly useful in various scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pause/Resume Animations and Videos:&lt;/strong&gt; Automatically pause animations or videos when the page is hidden and resume them when the page becomes visible again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize Resource Usage:&lt;/strong&gt; Save CPU and memory by stopping resource-intensive tasks when the user is not actively interacting with the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track User Engagement:&lt;/strong&gt; Monitor user engagement by tracking how often and how long users keep your webpage in focus.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By incorporating the Page Visibility API into your web applications, you can create a more efficient and user-friendly experience. Start using this powerful API today and take your web development skills to the next level!&lt;/p&gt;

&lt;p&gt;The Page Visibility API can be useful for scenarios where you want to pause or resume animations, videos, or other resource-intensive tasks based on whether the page is visible or not. It can also be used to track user engagement and optimize the user experience based on the visibility state of the page.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://hemantsharma.tech/blog/6" rel="noopener noreferrer"&gt;https://hemantsharma.tech/blog/6&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pagevisibilityapi</category>
      <category>javascript</category>
      <category>browserapis</category>
    </item>
    <item>
      <title>sessionStorage in browsers</title>
      <dc:creator>Hemant Sharma</dc:creator>
      <pubDate>Mon, 12 Aug 2024 12:00:00 +0000</pubDate>
      <link>https://dev.to/hemants1703/sessionstorage-in-browsers-2a39</link>
      <guid>https://dev.to/hemants1703/sessionstorage-in-browsers-2a39</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F080ygu0u84qy0y3e3sq7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F080ygu0u84qy0y3e3sq7.png" alt="sessionStorage in browsers" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;sessionStorage is a type of web storage that allows JavaScript sites and apps to store and access data right in the browser with an expiration date. This means the data stored in the browser will be available only for the duration of the page session.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use sessionStorage
&lt;/h2&gt;

&lt;p&gt;sessionStorage is similar to localStorage, but the data stored using sessionStorage is available only for the duration of the page session. This means the data will be cleared when the page session ends, i.e., when the browser window is closed.&lt;/p&gt;

&lt;p&gt;Here is an example of how to use sessionStorage to store and retrieve data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Store data in sessionStorage&lt;/span&gt;
&lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Retrieve data from sessionStorage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: John Doe&lt;/span&gt;

&lt;span class="c1"&gt;// Remove data from sessionStorage&lt;/span&gt;
&lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Clear all data from sessionStorage&lt;/span&gt;
&lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's see it in action
&lt;/h2&gt;

&lt;p&gt;To see sessionStorage in action, let's create a simple web page that stores and retrieves data using sessionStorage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;sessionStorage Example&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter your name"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"storeData()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Store Data&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"retrieveData()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Retrieve Data&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"removeItem()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Remove Data&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;storeData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;retrieveData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sessionStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation Example
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Browser Console output
&lt;/h2&gt;

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

&lt;p&gt;In this example, we have created a simple web page that allows users to store and retrieve data using sessionStorage. The data stored using sessionStorage will be available only for the duration of the page session.&lt;/p&gt;

&lt;h2&gt;
  
  
  See sessionStorage in browser DevTools
&lt;/h2&gt;

&lt;p&gt;You can also interact with sessionStorage using the browser DevTools. To access sessionStorage in the DevTools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open the browser DevTools by pressing &lt;code&gt;F12&lt;/code&gt; or right-clicking on the page and selecting "Inspect".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the "Application" tab in the DevTools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the "Storage" section, you will find "sessionStorage" under the "Storage" section. Here, you can see the data stored using sessionStorage and interact with it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's an example of how sessionStorage looks in the browser DevTools:&lt;/p&gt;

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

&lt;p&gt;That's it! You have learned how to use sessionStorage to store and access data in the browser with an expiration date. Try using sessionStorage in your web projects to store temporary data that is available only for the duration of the page session.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;sessionStorage is a useful feature in web development that allows you to store and access data in the browser with an expiration date. It is similar to localStorage but has a shorter lifespan, making it ideal for storing temporary data that is required only for the duration of the page session. Use sessionStorage in your web projects to enhance user experience and improve data management.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://hemantsharma.tech/blog/5" rel="noopener noreferrer"&gt;https://hemantsharma.tech/blog/5&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sessionstorage</category>
      <category>webstorage</category>
      <category>javascript</category>
    </item>
    <item>
      <title>localStorage in browsers</title>
      <dc:creator>Hemant Sharma</dc:creator>
      <pubDate>Mon, 12 Aug 2024 12:00:00 +0000</pubDate>
      <link>https://dev.to/hemants1703/localstorage-in-browsers-56fc</link>
      <guid>https://dev.to/hemants1703/localstorage-in-browsers-56fc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjp3mt95xyhhebhsml0se.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjp3mt95xyhhebhsml0se.webp" alt="localStorage in browsers" width="434" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;localStorage is a type of web storage that allows JavaScript sites and apps to store and access data right in the browser with no expiration date. This means the data stored in the browser will persist even after the browser window is closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use localStorage
&lt;/h2&gt;

&lt;p&gt;localStorage is a simple key-value store that can be used to store data in the browser. It is supported by all modern browsers and can be accessed using the &lt;code&gt;localStorage&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Here is an example of how to use localStorage to store and retrieve data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Store data in localStorage&lt;/span&gt;
&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Retrieve data from localStorage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: John Doe&lt;/span&gt;

&lt;span class="c1"&gt;// Remove data from localStorage&lt;/span&gt;
&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Clear all data from localStorage&lt;/span&gt;
&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's see it in action
&lt;/h2&gt;

&lt;p&gt;To see localStorage in action, let's create a simple web page that stores and retrieves data using localStorage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;localStorage Example&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter your name"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"storeData()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Store Data&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"retrieveData()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Retrieve Data&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"removeItem()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Remove Data&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;storeData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;retrieveData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Data removed from localStorage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implemented Example
&lt;/h2&gt;

&lt;p&gt;In this example, we have created a simple web page with an input field to enter a name and two buttons to store and retrieve the name using localStorage.&lt;/p&gt;

&lt;h2&gt;
  
  
  See localStorage in browser DevTools
&lt;/h2&gt;

&lt;p&gt;You can also see the data stored in localStorage using the browser DevTools. Here's how you can do it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open the browser DevTools by either pressing &lt;code&gt;F12&lt;/code&gt; or right-clicking on the page and selecting "Inspect".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the "Application" or "Storage" tab in the DevTools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the "Storage" section, you will see "Local Storage" on the left-hand side. Click on it and select the domain/URL you wish to view the data stored in localStorage for.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You should see the data stored in localStorage, including the key-value pairs that you have set.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h2&gt;
  
  
  Pros and Cons of localStorage
&lt;/h2&gt;

&lt;p&gt;| Pros | Cons | | --- | --- | | &lt;strong&gt;Persistence&lt;/strong&gt; — Data stored in localStorage persists after the browser window is closed. Ideal for user preferences, session data, and other cross-session information. | &lt;strong&gt;Security&lt;/strong&gt; — Data is not encrypted and can be read by anyone with browser access. Do not store passwords, payment details, or other sensitive data. | | &lt;strong&gt;Simple API&lt;/strong&gt; — Straightforward key-value &lt;code&gt;setItem&lt;/code&gt;, &lt;code&gt;getItem&lt;/code&gt;, and &lt;code&gt;removeItem&lt;/code&gt; methods with a low learning curve. | &lt;strong&gt;No expiration&lt;/strong&gt; — Entries persist until explicitly removed, which can lead to stale data or storage bloat without cleanup. | | &lt;strong&gt;Large capacity&lt;/strong&gt; — Typically ~5–10 MB per origin, far more than cookies for client-side storage needs. | &lt;strong&gt;Same-origin only&lt;/strong&gt; — Data is scoped to protocol, domain, and port; not shared across origins or subdomains by default. |&lt;/p&gt;

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

&lt;p&gt;localStorage is a powerful feature that allows web developers to store data in the browser without any expiration date. It can be used to store user preferences, session data, and other information that needs to persist across browser sessions.&lt;/p&gt;

&lt;p&gt;I hope this article has helped you understand how localStorage works and how you can use it in your web applications. If you have any questions or feedback, feel free to leave a comment below.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://hemantsharma.tech/blog/4" rel="noopener noreferrer"&gt;https://hemantsharma.tech/blog/4&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localstorage</category>
      <category>webstorage</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
