<SYSTEM>This document contains comprehensive information about K I R A N's professional profile, portfolio, and blog content. It includes personal details, work experience, projects, achievements, and all published blog posts. This data is formatted for consumption by Large Language Models (LLMs) to provide accurate and up-to-date information about K I R A N's background, skills, and expertise as a Full Stack Developer.</SYSTEM>

# kiranism.dev

> A minimal, pixel-perfect dev portfolio and blog to showcase my work as a Full Stack Developer.

## About

- Full-stack developer focused on building <span class="relative mx-0.1 inline-block whitespace-nowrap px-0.1 font-semibold text-foreground">scalable, maintainable</span> web applications.
- Skilled in Next.js, React, and TypeScript, with experience building high-quality, user-centric web applications using modern front-end tools.
- <span class="relative mx-0.1 inline-block whitespace-nowrap px-0.1 font-semibold text-foreground">AI-assisted</span> workflows to speed up development, improve code quality, and optimize technical decisions.
- Prioritize maintainable codebases, solid architecture, and real-world performance stability.
- Collaborated with <span class="mx-0.5 inline-flex items-center gap-1 align-middle"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none" class="size-4 shrink-0"><circle cx="64" cy="64" r="20" fill="#6C47FF"></circle><path fill="#BAB1FF" d="M99.572 10.788c1.999 1.34 2.17 4.156.468 5.858L85.424 31.262c-1.32 1.32-3.37 1.53-5.033.678A35.846 35.846 0 0 0 64 28c-19.882 0-36 16.118-36 36a35.846 35.846 0 0 0 3.94 16.391c.851 1.663.643 3.712-.678 5.033L16.646 100.04c-1.702 1.702-4.519 1.531-5.858-.468C3.974 89.399 0 77.163 0 64 0 28.654 28.654 0 64 0c13.163 0 25.399 3.974 35.572 10.788Z"></path><path fill="#6C47FF" d="M100.04 111.354c1.702 1.702 1.531 4.519-.468 5.858C89.399 124.026 77.164 128 64 128c-13.164 0-25.399-3.974-35.572-10.788-2-1.339-2.17-4.156-.468-5.858l14.615-14.616c1.322-1.32 3.37-1.53 5.033-.678A35.847 35.847 0 0 0 64 100a35.846 35.846 0 0 0 16.392-3.94c1.662-.852 3.712-.643 5.032.678l14.616 14.616Z"></path></svg><span class="relative inline-block whitespace-nowrap px-0.5 font-semibold text-foreground">Clerk<svg viewBox="0 0 100 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="pointer-events-none absolute left-0 top-[70%] h-2.5 w-full text-[#6C47FF]/90" preserveAspectRatio="none"><path d="M0 10L5 2L10 18L15 5L20 15L25 5L30 15L35 5L40 15L45 5L50 15L55 5L60 15L65 5L70 15L75 5L80 15L85 5L90 15L95 5L100 10" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path></svg></span></span> and <span class="mx-0.5 inline-flex items-center gap-1 align-middle"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 227" fill="none" class="size-4 shrink-0 text-[#362D59] dark:text-white"><path d="M148.367708,12.4025287 C144.036807,5.21480737 136.258026,0.820118864 127.866362,0.820118864 C119.474697,0.820118864 111.695916,5.21480737 107.365016,12.4025287 L73.6403017,70.165071 C126.066153,96.3390588 160.689085,148.341727 164.615024,206.806542 L140.93597,206.806542 C137.017513,156.694333 106.874845,112.396698 61.6982677,90.3588968 L30.4849684,144.32869 C55.8497707,155.704426 73.6778379,179.211206 77.7918243,206.704035 L23.4120041,206.704035 C22.1018479,206.611361 20.9266153,205.864669 20.2861278,204.71799 C19.6456403,203.571311 19.6261529,202.179068 20.2342955,201.014912 L35.3027847,175.388229 C30.1976229,171.128798 24.3630321,167.829476 18.0816541,165.65009 L3.16692493,191.276772 C0.0305635285,196.656417 -0.818661742,203.068719 0.809210488,209.079324 C2.43708272,215.08993 6.40620885,220.197261 11.8287436,223.258872 C15.3657216,225.251729 19.3523095,226.310116 23.4120041,226.334074 L97.8831433,226.334074 C100.696274,191.620878 85.1423372,157.966047 56.8804514,137.614499 L68.7199787,117.113153 C104.398813,141.618242 124.473737,183.151896 121.510945,226.334074 L184.603837,226.334074 C187.593899,160.904124 155.557278,98.8221906 100.497065,63.3483734 L124.432386,22.3456815 C125.542508,20.4856859 127.944329,19.8680747 129.81399,20.9618406 C132.530418,22.4481882 233.807067,199.169791 235.703442,201.219925 C236.383476,202.439289 236.358897,203.929352 235.639016,205.125624 C234.919136,206.321896 233.614065,207.041397 232.218213,207.011555 L207.821611,207.011555 C208.129131,213.537817 208.129131,220.046994 207.821611,226.539592 L232.32072,226.539592 C238.604421,226.580218 244.643414,224.105731 249.091568,219.667205 C253.539722,215.228679 256.027289,209.195062 256,202.911286 C256.002825,198.802186 254.905596,194.767215 252.822066,191.225519 L148.367708,12.4025287 Z" fill="currentColor"></path></svg><span class="relative inline-block whitespace-nowrap px-0.5 font-semibold text-foreground">Sentry<svg viewBox="0 0 100 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="pointer-events-none absolute left-0 top-[70%] h-2.5 w-full text-[#362D59]/90" preserveAspectRatio="none"><path d="M0 10L5 2L10 18L15 5L20 15L25 5L30 15L35 5L40 15L45 5L50 15L55 5L60 15L65 5L70 15L75 5L80 15L85 5L90 15L95 5L100 10" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path></svg></span></span> growth teams on an OSS Next.js starter template.
- Maintaining an Next.js starter template used by many developers, with <span data-github-stars-inline="true"></span>.

### Personal Information

- First Name: K I R A N
- Last Name: K I R A N
- Display Name: K I R A N
- Location: Remote, Kerala
- Website: https://dub.sh/kiranism

### Social Links

- [GitHub](https://github.com/Kiranism)
- [X](https://twitter.com/thedvlpr)
- [LinkedIn](https://www.linkedin.com/in/kiranism/)

### Tech Stack

- [TypeScript](https://www.typescriptlang.org/)
- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
- [Node.js](https://nodejs.org/)
- [Bun](https://bun.sh/)
- [React](https://react.dev/)
- [Next.js](https://nextjs.org/)
- [Tailwind CSS](https://tailwindcss.com/)
- [shadcn/ui](https://ui.shadcn.com/)
- [Base UI](https://base-ui.com/)
- [Motion](https://motion.dev/)
- [TanStack](https://tanstack.com/)
- [Git](https://git-scm.com/)
- [Docker](https://www.docker.com/)
- [MongoDB](https://www.mongodb.com/)
- [Figma](https://www.figma.com/)
- [Claude](https://claude.ai/)
- [Cursor](https://cursor.com/)
- [ChatGPT](https://chatgpt.com/)

## Experience

### Fullstack Developer | Hutfin

Duration: 01.2025 - Present

Skills: Nextjs, React, TypeScript, Node, Express, MongoDB, Aws

Responsibilities:
- Working as a founding engineer for a commercial real estate platform.

Location:
- Remote · United States

### Contract Remote Developer | Upwork / Freelancer

Duration: 04.2023 - Present

Skills: Nextjs, React, TypeScript, TailwindCSS, Shadcn, Supabase, Clerk, Frontegg, Tanstack query, Tanstack tables

Responsibilities:
- Deliver contract projects for international clients across Switzerland, the US, and Belgium using a modern Next.js + TypeScript stack, with a focus on scalable SaaS delivery, frontend architecture, and reliable full-stack execution.

Location:
- Remote · Switzerland, United States, Belgium

### Contract Full Stack Engineer - Next.js (short-term) | ROQ.tech

Duration: 04.2024 - 05.2024

Skills: Nextjs, React, TypeScript, Prisma, Postgres, Zod, TailwindCSS, Shadcn

Responsibilities:
- Completed a short-term contract engagement on a full-stack project using Next.js, Server Actions, TypeScript, Prisma, Postgres, shadcn/ui, and Tailwind CSS.

Location:
- Remote · Germany

### Frontend Developer | Avaibe

Duration: 01.2024 - 01.2025

Skills: Nextjs, React, TypeScript, TailwindCSS, Payload CMS, AWS

Responsibilities:
- Teamed up with developers, designers, and stakeholders to build and launch SaaS products.

Location:
- United Arab Emirates

### Fullstack Developer - MERN | Stackroots

Duration: 01.2022 - 04.2023

Skills: Nextjs, React, Material UI, Nodejs, MongoDB, Express

Responsibilities:
- Collaborated with developers and designers to ship production web applications solving real business problems.

Location:
- Hybrid · India

## Projects

### Next Shadcn Dashboard

Project URL: https://dub.sh/shadcn-dashboard/

Skills: Next.js, TypeScript, shadcn/ui, TanStack Table, Clerk, Sentry, Zod, Nuqs, Husky, Eslint

Open source admin dashboard starter built with Next.js, shadcn/ui, Tailwind CSS, and TypeScript.

Sponsorships:
- Collaborated on Sentry and Clerk integration support for this dashboard starter.

### what the link

Project URL: undefined

Skills: TanStack Start, Hono, Drizzle ORM, SQLite, TypeScript, shadcn/ui, Baileys, OpenRouter, Gemini

A bookmark manager built for people who save links in WhatsApp. AI-powered semantic search, auto-tagging, and link extraction — all from a WhatsApp group.

### TanStack Dashboard

Project URL: https://dub.sh/tanstack-start-dashboard

Skills: TanStack Start, TypeScript, shadcn/ui, TanStack Query

Admin dashboard starter built with TanStack Start and shadcn/ui for scalable React app foundations.

### next-resume-ai

Project URL: https://github.com/Kiranism/next-resume-ai

Skills: Next.js, TypeScript, React PDF, Tailwind CSS, Shadcn-ui, Gemini, Drizzle, Neon Postgres

Resume generator built with Next.js and React PDF to create and export polished resumes quickly.

### ChatWithPDF

Project URL: https://chat-with-pdf7.vercel.app

Skills: Next.js, TypeScript, OpenAI, LangChain, Pinecone

AI-powered PDF workflow to upload documents, summarize content, and answer questions with LLM-backed retrieval.

### codeFlow

Project URL: https://github.com/Kiranism/codeFlow

Skills: TypeScript, React, Remotion

Developer tool to animate and present code snippets with clean visual storytelling.

### WallpaperNext

Project URL: https://wallpapernext.in

Skills: Next.js, Sanity, Tailwind CSS

Wallpaper platform optimized for SEO and content workflows with Sanity CMS and high-speed downloads.

### Draw-X

Project URL: https://draw-x-production.up.railway.app/

Skills: Next.js, Socket.io, Zustand, Tailwind CSS, Zod

Realtime collaborative drawing app with customizable strokes, undo/redo, and multiplayer socket synchronization.

### next-shadcn-dashboard-starter

Project URL: https://github.com/Kiranism/next-shadcn-dashboard-starter

Skills: admin-dashboard, dashboard-template, nextjs, nextjs-admin, nextjs-dashboard, nextjs-starter, shadcn-admin, shadcn-ui, tailwindcss

Open source admin dashboard starter built with Next.js 16, shadcn/ui, Tailwind CSS, and TypeScript.

### tanstack-start-dashboard

Project URL: https://github.com/Kiranism/tanstack-start-dashboard

Skills: react-admin-dashboard, react-dashboard, tanstack-react-query, tanstack-start, tanstack-start-dashboard, tanstack-table

Admin Dashboard Starter with Tanstack Start + Shadcn Ui

### voicenotes

Project URL: https://github.com/Kiranism/voicenotes

Skills: TypeScript

No description provided.

### Kiranism

Project URL: https://github.com/Kiranism/Kiranism

Skills: Code

No description provided.

### testing-env

Project URL: https://github.com/Kiranism/testing-env

Skills: HTML

No description provided.

### Thalaforareason

Project URL: https://github.com/Kiranism/Thalaforareason

Skills: ai, drizzle, neondb, nextjs, openai, postgres, typescript, vercel

This app is made with love, dedicated to India's legendary captain, Ms. Dhoni.

### Next-auth-custom-backend

Project URL: https://github.com/Kiranism/Next-auth-custom-backend

Skills: TypeScript

Next.js app setup when you've got a separate backend server and Next.js as your frontend!

### turbo-ecom

Project URL: https://github.com/Kiranism/turbo-ecom

Skills: ecommerce, nextjs, shadcn-ecommerce, turborepo, turborepo-shadcn, turborepo-starter

A Turbo monorepo e-commerce project with admin and user apps, sharing a common database, Prisma schema, and Shadcn UI components across apps.

### job-board

Project URL: https://github.com/Kiranism/job-board

Skills: TypeScript

Develop a CRUD application for a job listing board as a part of an assessment using React with TypeScript, incorporating a mock API and styling with Tailwind CSS.

### dashbow

Project URL: https://github.com/Kiranism/dashbow

Skills: TypeScript

Crud Assessment

### react-ecom

Project URL: https://github.com/Kiranism/react-ecom

Skills: JavaScript

No description provided.

### holymollyproblems

Project URL: https://github.com/Kiranism/holymollyproblems

Skills: JavaScript

No description provided.

### weather-app

Project URL: https://github.com/Kiranism/weather-app

Skills: JavaScript

Weather App Demo Link

### board

Project URL: https://github.com/Kiranism/board

Skills: JavaScript

This project is a dashboard application built using React, Next.js, Tailwind CSS, and NextAuth.js for authentication. It provides a pixel-perfect replica of the design provided, with responsive UI and working charts populated with dummy data from the Mocki fake API.

### Taiyo-ts

Project URL: https://github.com/Kiranism/Taiyo-ts

Skills: TypeScript

This is a contact management app built with ReactJS, TypeScript, TailwindCSS, React Router v6, and React Query. It includes features such as adding new contacts, displaying a list of contacts, editing and deleting contacts, and integrating charts and maps for COVID-19 data.

### mui-with-tailwindcss

Project URL: https://github.com/Kiranism/mui-with-tailwindcss

Skills: JavaScript

No description provided.

### thirdlens-tgbot

Project URL: https://github.com/Kiranism/thirdlens-tgbot

Skills: JavaScript

No description provided.

### night-watchers-vscode-theme

Project URL: https://github.com/Kiranism/night-watchers-vscode-theme

Skills: dark-teme, editors-theme, tutorial-theme, vscode, vscode-theme

🌌 NIGHT WATCHERS: A VS Code dark theme for contrast for night and daytime coding, 🎃

### vite-app-starter

Project URL: https://github.com/Kiranism/vite-app-starter

Skills: JavaScript

No description provided.

### React-Frontend-Task

Project URL: https://github.com/Kiranism/React-Frontend-Task

Skills: JavaScript

No description provided.

### React-challanges

Project URL: https://github.com/Kiranism/React-challanges

Skills: JavaScript

No description provided.

### task

Project URL: https://github.com/Kiranism/task

Skills: JavaScript

No description provided.

### olx-clone

Project URL: https://github.com/Kiranism/olx-clone

Skills: firebase, firebase-auth, react

Olx clone using React and Firebase

### movie-redux

Project URL: https://github.com/Kiranism/movie-redux

Skills: api, react, redux, scss

A movie app with redux, fetching data from omtb api

### chat-app

Project URL: https://github.com/Kiranism/chat-app

Skills: node, react, socketio

A real time chat application using react node and socket io

### Ecom

Project URL: https://github.com/Kiranism/Ecom

Skills: nodejs, react

A fully functional Ecommerce Website Using React and Nodejs

### TravelAdvisor

Project URL: https://github.com/Kiranism/TravelAdvisor

Skills: google-maps-api, reactjs

A Travel Companion Application using React and Google Map Api

### newsanddeals

Project URL: https://github.com/Kiranism/newsanddeals

Skills: css, django, html, python

NewsandDeals

### MovieApp

Project URL: https://github.com/Kiranism/MovieApp

Skills: JavaScript

React-Native Movie app

### emergency-portal

Project URL: https://github.com/Kiranism/emergency-portal

Skills: JavaScript

No description provided.

### mern-store-client

Project URL: https://github.com/Kiranism/mern-store-client

Skills: expressjs, mongodb, nodejs, reactjs

An Ecommerce store using Mern Stack

### crm-frontend

Project URL: https://github.com/Kiranism/crm-frontend

Skills: Code

No description provided.

### 50days50projects

Project URL: https://github.com/Kiranism/50days50projects

Skills: CSS

#50days50 javascript project

### React-ToDoApp

Project URL: https://github.com/Kiranism/React-ToDoApp

Skills: JavaScript

A simple ToDoApp using React

### responsive-photography-web

Project URL: https://github.com/Kiranism/responsive-photography-web

Skills: HTML

No description provided.

### single-page-webpage

Project URL: https://github.com/Kiranism/single-page-webpage

Skills: HTML

created a single page webpage about a mobile phone and its features


## Blog

---
title: "What The Link — How I AI-fied My WhatsApp"
description: "How I built a WhatsApp-powered bookmark manager with semantic search using Baileys, TanStack Start, Hono, Drizzle, SQLite, and Gemini embeddings."
last_updated: "April 14, 2026"
source: "https://kiranism.dev/blog/what-the-link"
---

# What The Link — How I AI-fied My WhatsApp

How I built a WhatsApp-powered bookmark manager with semantic search using Baileys, TanStack Start, Hono, Drizzle, SQLite, and Gemini embeddings.

Have you ever used WhatsApp as a bookmarking tool?

I did. I even have a group with just me where I dump thoughts and links. Blogs. Tweets. Tools. Products. Ideas.

The problem? Finding them again. So I built **What The Link**.

## The Problem

I'm someone who always sends links — products, blogs, brainstorming notes, ideas — everything gets dumped into my WhatsApp personal group for quick access.

But over time it gets flooded. Messy. Hard to search.

You need to remember *what* you're searching for. There's no semantic search in WhatsApp — and that's the real problem. You have to remember the exact words you used, not what the link was actually about.

So I thought — why not give WhatsApp that ability? AI-ify it. Make it actually smart.

## Finding the Right Approach

When the idea first clicked, I thought about piping chats from WhatsApp to Notion. But only paid bot integrations existed. Same story with the WhatsApp Business API — also paid. Not what I needed.

Then I got curious about how **OpenClaw** handles their WhatsApp integration. That's how I found **Baileys** — an unofficial WhatsApp socket package where you connect your WhatsApp via QR code and keep the session active. For free.

That was exactly what I wanted.

I didn't go through every file in their big monorepo to understand the codebase. What I did instead was prepend their GitHub link with **DeepWiki**, which had already indexed the whole repo. I just chatted with it and got the info I needed.

```text
deepwiki.com/[github-repo-url]
```

Around the same time I got the **Claude Max plan** for 6 months through Anthropic's open source program. What else do you want, right? I started building, planning, and figuring out the scope.

## The Stack

I picked the **T3 stack builder** to bootstrap the project. The full stack:

* **TanStack Start** — full-stack React framework, file-based routing, server functions built in
* **Hono** — lightweight backend API layer
* **Drizzle ORM** — type-safe, SQL-first. Pairs really well with SQLite
* **SQLite** — simple, file-based, no infra overhead for a v1
* **shadcn/ui** — for the UI components

The whole thing is type-safe end to end. Drizzle handles the schema and queries, Hono handles the API routes, TanStack Start ties the frontend and backend together. For a solo project it's a really clean setup — not over-engineered, but also not something you'll regret later.

## Building It — V1 First

I didn't try to build everything at once.

**V1 was just the CRUD and the backend.** Baileys listens for incoming WhatsApp messages, extracts any links, and saves them to SQLite via Drizzle. That's it. No AI yet. Just — capture the link, store it, show it in the web UI.

Get the plumbing right first. The smart stuff comes after.

```text
WhatsApp message
    ↓
Baileys socket picks it up
    ↓
Extract the link
    ↓
Save to SQLite via Drizzle
    ↓
Show in the web UI
```

Once that was working cleanly, I moved to the part I actually wanted to build.

## How the WhatsApp Part Actually Works

Baileys gives you a raw WebSocket connection to WhatsApp Web. You scan a QR code once, and the session gets persisted to disk — so on restart, it just reconnects. No QR again.

But it's not plug-and-play. WhatsApp rejects outdated client versions with a `405`. So the server fetches the latest WhatsApp Web version from a public repo on startup. If that fails, it falls back to a hardcoded version. Small detail, but without it — the whole thing breaks silently.

### Connection state machine

```text
connection === "open"   → good, listening
connection === "close"  → check why
  └─ 405 → outdated version → refetch, retry in 10s
  └─ 440 → another device took over → logout, clear auth, wait
  └─ anything else → auto-reconnect after 5s
```

### Message pipeline

When a message comes in, it goes through a pipeline:

1. Ignore your own messages
2. Filter by allowed group (optional — you can lock it to one group)
3. Check if it's a command (`?help`, `?search query`)
4. If not — extract links and save

### Link extraction — three-layer fallback

```text
Layer 1: Regex — fast, free. Grabs any http/https URL from the text.
Layer 2: AI text extraction — if regex finds nothing, ask Gemini to parse the message.
Layer 3: AI vision — if the message has an image (screenshot, photo of a screen,
         QR code), download it and run it through Gemini's vision model.
```

Most messages hit Layer 1 and move on. But the vision layer is surprisingly useful — people screenshot tweets and product pages all the time. That would've been lost without it.

URLs also get cleaned up before saving. Tracking parameters like `utm_source`, `fbclid`, `gclid` — 21 of them — get stripped. Trailing slashes removed. URLs normalized. No junk in the database.

### Reactions

When a link is saved, the bot reacts to the WhatsApp message:

| Reaction | Meaning                                   |
| -------- | ----------------------------------------- |
| 🔖       | Saved                                     |
| ⚠️       | Duplicate                                 |
| 📝       | Saved as a note (no URL found, just text) |
| ❌        | Nothing useful in the message             |

Small touch but it makes it feel alive.

## The Database — Keeping It Simple

The whole thing runs on one SQLite file. Bookmarks, tags, settings, embeddings — all in one place.

### Bookmarks table

```sql
CREATE TABLE bookmarks (
  url           TEXT UNIQUE,
  title         TEXT,
  description   TEXT,
  image         TEXT,
  favicon       TEXT,
  domain        TEXT,
  tags          TEXT,      -- JSON array, denormalized
  source        TEXT,      -- 'whatsapp' | 'manual' | 'import'
  whatsapp_message_id TEXT,
  summary       TEXT,      -- AI-generated
  embedding     BLOB,      -- serialized Float32Array
  metadata_status   TEXT,
  summary_status    TEXT,
  embedding_status  TEXT,
  is_archived   INTEGER DEFAULT 0,
  created_at    TEXT,
  updated_at    TEXT
);
```

Each bookmark has three independent processing pipelines — metadata, summary, and embedding — each with their own status and retry count. They run independently so a failed summary doesn't block the embedding, and vice versa.

Tags are stored as a JSON array directly on the bookmark. No join table. For this scale it's the right call — simpler queries, simpler code, and SQLite's `json_each()` handles filtering just fine.

There's also a separate `tags` table that tracks tag names and usage counts — basically a tag cloud index. And an `app_settings` key-value table for things like which WhatsApp group to listen to and digest preferences.

No foreign keys. No complex relationships. One file, easy to back up, easy to move.

## Then I AI-fied It

After V1 was solid, I added **OpenRouter** using the OpenAI SDK — so I'm hitting any model I want through one unified API without being locked into one provider.

The AI layer is a singleton client — it only initializes if you set the `OPENROUTER_API_KEY`. No key? No AI. The app still works, you just get basic keyword search instead of semantic search. Graceful degradation.

When a bookmark is saved, three things happen asynchronously — fire-and-forget style. The user sees the bookmark immediately. The AI stuff runs in the background:

### 1. Metadata fetch

Cheerio crawls the page — grabs the title, description, OG image, favicon. If the crawl gets blocked (some sites do), it falls back to AI inference from just the URL structure. Like `instagram.com/p/XYZ` — even without crawling, you know it's an Instagram post.

### 2. Summary + Tags

The page content gets sent to Gemini via OpenRouter. Two parallel prompts:

* **Summary:** "Summarize in 4-5 concise lines." Capped at 2000 characters.
* **Tags:** "Suggest 2-4 short lowercase tags." Returns a JSON array.

Tags only auto-generate if the user didn't include hashtags in the original message. If you send `#design #tools https://some-link.com`, those hashtags become the tags and the AI doesn't override them.

### 3. Embedding

Once the summary exists, the app builds an embedding text by concatenating:

```text
Title | Tags: tag1, tag2 | Full summary | Description (truncated to 500 chars)
```

That combined text gets sent to `gemini-embedding-001` via OpenRouter. The embedding comes back as a `Float32Array`, gets serialized to a binary blob, and stored directly in the SQLite `embedding` column.

No separate vector database. No Pinecone. No Qdrant. Just a BLOB column in the same SQLite file.

## The Retry Machine

AI APIs fail. Rate limits hit. Pages block crawlers. So every pipeline has a retry system.

Three cron jobs run every 5 minutes:

| Job                 | What it does                                                                                                              |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Summary Retry**   | Picks up `pending` or `failed` summaries (batch of 50), generates summary + auto-tags, chains to embedding job on success |
| **Embedding Retry** | Picks up `pending` or `failed` embeddings (batch of 50), generates and caches embedding                                   |
| **Metadata Retry**  | Retries failed page crawls                                                                                                |

Each bookmark gets 3 retries max per pipeline. After that it's marked `failed` and left alone — you can manually retry from the UI.

Rate limits get special treatment. When the app hits a `429` from OpenRouter, it pauses the entire batch for 60 seconds, then resumes. No aggressive retries hammering the API.

On startup, it also runs an immediate pass — processes all pending items before the cron kicks in. So after a deploy or restart, everything catches up fast.

## How Semantic Search Actually Works

This is the part that makes the whole thing worth building.

The approach: **in-memory embedding cache**. On startup, the server loads every bookmark's embedding into memory as `Float32Array` objects. When you search, it:

1. Generates an embedding for your query (one API call)
2. Computes cosine similarity against every cached embedding
3. Filters results above a 0.55 threshold
4. Returns top 50, sorted by score

Cosine similarity is just dot product divided by the product of magnitudes. Three lines of math on `Float32Array`. For a few thousand bookmarks, it runs in about **2ms**. No need for a vector index.

If AI isn't configured or the cache is empty, search falls back to SQL `LIKE` queries across title, description, URL, summary, and even inside the JSON tags array using `json_each()`. Not as smart, but it works.

### Search from WhatsApp

You can search directly from WhatsApp too:

| Command          | Action            |
| ---------------- | ----------------- |
| `?indie hackers` | Semantic search   |
| `?#design`       | Tag filter        |
| `?recent 10`     | Last 10 bookmarks |

Results come back as WhatsApp messages with clickable links. So you don't even need to open the web UI.

## The Web UI

In January I came across **Zaid Alam's** bookmarking tool. He built it for his own use case. I loved the minimal UI so I took some inspiration — and a screenshot — and prompted it straight into Claude Code.

It's not open source so I couldn't clone it. So I just fired more tokens and built the web UI myself using **shadcn components**.

The frontend is a TanStack Start app — SSR on first load, then SPA navigation. Two routes:

* **Home** — the bookmark library. Search bar, tag filters, pagination (25 or 50 per page), data grid with title, domain, date, and actions. Keyboard shortcuts for everything: `Cmd+K` to focus search, arrow keys to navigate, `Delete` to remove.
* **Settings** — WhatsApp QR code display, group selection dropdown, daily digest toggle with hour picker. Connect, disconnect, reconnect — all from the browser.

Auth is simple. Single password. Cookie-based session that lasts 30 days. Rate limited to 10 failed attempts per IP per 15 minutes. No OAuth, no user management. It's a personal tool.

## Deployment

The whole thing ships as a Docker image. Multi-stage build — builder compiles everything, runner is Alpine with just the compiled output.

```bash
docker-compose up -d
```

One volume mount for `/data` — that's where the SQLite database and the WhatsApp auth session live. Survives restarts, easy to backup, easy to migrate.

There's also a one-liner install script for VPS setups. It checks for Docker, asks for your password, builds the image, and starts the container. I'm running mine on Oracle Cloud's Always Free ARM instance — 24GB RAM for a SQLite app is hilariously overkill but hey, it's free.

Health check pings `/health` every 30 seconds. Container auto-restarts unless you explicitly stop it.

## Architecture at a Glance

```text
┌──────────────────────────────────────────────────────────────────┐
│                          WhatsApp                                │
│                     (Baileys Socket)                             │
│    QR Auth ←→ Session Persistence ←→ Message Listener           │
└──────────────┬───────────────────────────────────────────────────┘
               │ messages.upsert
               ▼
┌──────────────────────────────────────────────────────────────────┐
│                    Link Extraction Pipeline                      │
│         Regex → AI Text → AI Vision (3-layer fallback)          │
└──────────────┬───────────────────────────────────────────────────┘
               │
               ▼
┌──────────────────────────────────────────────────────────────────┐
│                     SQLite (via Drizzle)                         │
│              bookmarks │ tags │ app_settings                     │
│                    Single .db file                                │
└──────────┬────────────────────────────────┬──────────────────────┘
           │                                │
           ▼                                ▼
┌─────────────────────────┐   ┌─────────────────────────────────┐
│   AI Processing (Async) │   │        Hono API Server          │
│  ┌───────────────────┐  │   │  /api/bookmarks  (CRUD+search)  │
│  │ Metadata (Cheerio) │  │   │  /api/whatsapp   (QR+status)   │
│  │ Summary (Gemini)  │  │   │  /api/settings   (config)       │
│  │ Tags (Gemini)     │  │   │  /api/login      (auth)         │
│  │ Embedding (Gemini)│  │   └──────────┬──────────────────────┘
│  └───────────────────┘  │              │
│  Retry cron: */5 * * *  │              ▼
└─────────────────────────┘   ┌─────────────────────────────────┐
                              │     TanStack Start (React)       │
           ┌──────────────────│  SSR + SPA │ shadcn/ui           │
           │                  │  Search │ Settings │ Auth         │
           ▼                  └─────────────────────────────────┘
┌─────────────────────────┐
│  In-Memory Embedding    │
│  Cache (Float32Array)   │
│  Cosine Similarity      │
│  ~2ms search time       │
└─────────────────────────┘
```

***

That's the story. Built out of frustration, unlocked by one good insight, and shipped with a lot of tokens.


Last updated on April 14, 2026

---
title: "Efficient Type-Safe Server Actions in Next.js with ZSA"
description: "TL;DR: Discover how to implement type-safe server actions in your Next.js application using zsa or next-safe-action. This guide focuses on zsa, offering a clear approach to integrating type safety, validation, and error handling.
Introduction to Type..."
last_updated: "July 23, 2024"
source: "https://kiranism.dev/blog/efficient-type-safe-server-actions-in-nextjs-with-zsa"
---

# Efficient Type-Safe Server Actions in Next.js with ZSA

TL;DR: Discover how to implement type-safe server actions in your Next.js application using zsa or next-safe-action. This guide focuses on zsa, offering a clear approach to integrating type safety, validation, and error handling.
Introduction to Type...

> Imported from [Hashnode](https://kir4n.hashnode.dev/efficient-type-safe-server-actions-in-nextjs-with-zsa).

**TL;DR:** Discover how to implement type-safe server actions in your Next.js application using `zsa` or `next-safe-action`. This guide focuses on `zsa`, offering a clear approach to integrating type safety, validation, and error handling.

## Introduction to Type-Safe Server Actions in Next.js

In modern web development with Next.js, server actions are essential for handling form submissions and data mutations. While implementing these actions with robust type safety and validation can be challenging, utilizing libraries like [zsa](https://zsa.vercel.app/docs/introduction) or [next-safe-action](https://next-safe-action.dev/) can significantly simplify this process, enhancing both security and developer experience.

### What is ZSA and Why Use It?

**ZSA** is a library that integrates with Zod for schema validation in Next.js. It simplifies the creation of type-safe server actions and provides built-in support for handling input validation and error management.

By using `zsa`, you can avoid the complexity of manually setting up validation and error-handling utilities, streamlining your development process and improving code quality.

Enough theory! Now, I’ll walk you through how I used this with my Next.js blog application.

**Set Up Your Service Logic**

Normally, we create an `actions.ts` file inside a `lib` folder. However, to keep things cleaner, I’ll create a `service` folder as well, where we’ll create a `service.ts` file to handle the core logic for server-side operations. In this example, we’ll use Prisma ORM with PostgreSQL.

```typescript
// services/postService.ts
import { prisma } from '@/lib/prisma';

export async function savePostToDb(input: PostInput) {
  await prisma.post.create({
    data: {
      title: input.title,
      images: input.images,
    },
  });
}
```

**Define Server Actions and Middleware**

**Authentication Procedure**

In the action file where the action and ZSA logic take place, since our app has different roles such as admin and user, we need middleware to allow only admins to perform certain server actions. This can be achieved by creating procedures using ZSA.

First, we can create a procedure for authenticated users.

```typescript
//admin/action.ts
"use server" 
import { createServerAction, createServerActionProcedure } from 'zsa';
import { getUser } from '@/lib/lucia';

const authedProcedure = createServerActionProcedure().handler(async () => {
  try {
    const user = await getUser();

    if (!user) {
      throw new Error('User not authenticated');
    }

    return {
      user: {
        email: user.email,
        id: user.id,
        role: user.role
      }
    };
  } catch {
    throw new Error('User not authenticated');
  }
});
```

**Admin Check Procedure**

Extend the authentication procedure to include admin role verification:

```typescript
//admin/action.ts
"use server" 

import { createServerAction, createServerActionProcedure } from 'zsa';

const isAdminProcedure = createServerActionProcedure(authedProcedure).handler(
  async ({ ctx }) => {
    const role = ctx.user.role;

    if (role !== 'ADMIN') {
      throw new Error('User is not an admin');
    }

    return {
      user: {
        id: ctx.user.id,
        email: ctx.user.email,
        role: role
      }
    };
  }
);
```

Perfect, we have created a **middleware** that performs the `isAdmin` check, which we can chain with **admin-only server actions**.

```typescript
//admin/action.ts
"use server" 
import { createServerAction, createServerActionProcedure } from 'zsa';
import {
  POST_SCHEMA
} from '@/lib/validations';

// Only admins are allowed to create a post.
export const createPost = isAdminProcedure
  .createServerAction()
  .input(POST_SCHEMA, {
    type: 'formData'
  })
  .handler(async ({ input, ctx }) => {
    await savePostToDb(input);
    revalidatePath('/', 'layout');
    redirect('/admin/dashboard/posts');
  });
```

Now, we have successfully created a type-safe server action with the Zod schema for input validation, both on the server and the client.

On to the client section. To use the server action, there are several approaches outlined in the ZSA documentation. For now, I’m using my favorite approach.

```typescript
//form.tsx
"use client"
import { useServerAction } from 'zsa-react';

// you can use this hook inside the form function component just like how you do mutation with the react query.
 const { execute: createPostExe, isPending: loadingCreate } = useServerAction(
    createPost,
    {
      onSuccess: () => {
        toast.success('Post created successfully');
        form.reset();
      },
      onError: ({ err }) => {
        toast.error(err.message);
      }
    }
  );

 const onSubmit = async (values: POST_SCHEMA_VALUES) => {
    //! Files need to be sent as formData.
    const formData = new FormData();
    formData.append('title', values.title);
    for (let i = 0; i < values.images.length; i++) {
      formData.append('images', values.images[i], values.images[i].name);
    }
    const { images, ...rest } = values;
    await createPostExe(formData, rest);
  };

// rest of code...
<form onSubmit={form.handleSubmit(onSubmit)}
 className="w-full space-y-8">
```

If you are familiar with React Query mutations, this syntax will be familiar to you. This is how you can use it if you are using React Hook Form to validate input with the Zod schema on the client side. ZSA provides not only validation errors from the Zod schema but also server and fetch errors.

**That’s it!** Now you have successfully created and integrated a **type-safe server action** with proper **error handling** and **validation**. This is how I’m managing server actions for my recent projects.

The `next-safe-action` library also performs similar tasks and follows a similar syntax, so it doesn’t matter which one you choose. One thing I noticed is that if you are using server actions to fetch data in client components along with React Query, ZSA has a wrapper on top of React Query that you can use to fetch data on the client side. You can find more details in the zsa [documentation](https://zsa.vercel.app/docs/react-query)

Kudos to the creators and collaborators of both packages! Feel free to give them a star on GitHub.

**Happy Coding! 🥂**


Last updated on July 23, 2024

---
title: "How to Optimize Your React Application for SEO: Tips and Tricks"
description: "With more and more web applications being built using React, it is crucial to ensure that they are optimized for search engines. However, setting up SEO for React applications can be challenging due to the dynamic nature of client-side rendering. In ..."
last_updated: "November 14, 2023"
source: "https://kiranism.dev/blog/how-to-optimize-your-react-application-for-seo-tips-and-tricks"
---

# How to Optimize Your React Application for SEO: Tips and Tricks

With more and more web applications being built using React, it is crucial to ensure that they are optimized for search engines. However, setting up SEO for React applications can be challenging due to the dynamic nature of client-side rendering. In ...

> Imported from [Hashnode](https://kir4n.hashnode.dev/how-to-optimize-your-react-application-for-seo-tips-and-tricks).

With more and more web applications being built using React, it is crucial to ensure that they are optimized for search engines. However, setting up SEO for React applications can be challenging due to the dynamic nature of client-side rendering. In this blog post, we will discuss the struggles of setting up SEO in React applications, as well as the tips and tricks that can help improve your search engine rankings.

### **Solutions for Optimizing SEO in a React Application**

One of the main challenges of optimizing a React application for SEO is that it is primarily built using client-side rendering (CSR). This means that the content of the application is generated dynamically on the client-side using JavaScript. However, search engine crawlers are not designed to execute JavaScript, which can result in poor search engine rankings and lower visibility.

To overcome this challenge, one solution is to implement server-side rendering (SSR) techniques. By rendering the application on the server-side and sending the fully rendered HTML to the client, search engines can easily crawl and index the content of the application, resulting in better SEO.

There are several solutions available for implementing SSR in React applications, such as using frameworks like Next.js or Gatsby. However, implementing SSR can be time-consuming and may require a significant amount of development effort.

Another solution to consider when setting up SEO in a React application is to serve the app with an Express Node server. This approach can be particularly useful if you have an existing React application that you want to optimize for search engines. By using Express, you can configure the server to send the appropriate meta tags to the client-side React application. This allows the search engine crawlers to easily access the relevant information they need to properly index and rank the page. While this approach may require additional development effort to set up, it can be a powerful way to ensure your React application is optimized for SEO. Additionally, this solution can be combined with the previously mentioned solution of getting meta tags from an API and replacing the default index.html meta tags, to create an even more effective SEO solution for your React application.

### Solution breakdown

In addition, using a library like React Helmet can help you manipulate the metadata and head elements of a page, which can improve search engine rankings by ensuring that the relevant metadata is available to crawlers. However, while React Helmet is useful, it may not provide a complete solution for optimizing your React application for search engines.

To further optimize your React application for SEO, you can also:

* Ensure that your application has a clear and organized URL structure that reflects the content hierarchy

* Use descriptive, keyword-rich titles and meta descriptions for each page

* Use alt tags for images to help search engines understand the content of your pages

* Build high-quality, relevant backlinks to your application

* Regularly update your application with fresh, relevant content

In conclusion, optimizing your React application for SEO can be challenging, but there are several tips and tricks that can help you improve your search engine rankings. By implementing server-side rendering, using a library like React Helmet, and following best practices for SEO, you can increase the visibility and traffic to your web application.


Last updated on November 14, 2023

---
title: "Mui with Tailwind CSS"
description: "What is Mui?
Mui aka material UI is a React UI library that complements React for a fast build. It comes with pre-built React components and a default Material Styled Theme(which is very common in Google products). We can either keep the default them..."
last_updated: "December 20, 2022"
source: "https://kiranism.dev/blog/mui-with-tailwind-css"
---

# Mui with Tailwind CSS

What is Mui?
Mui aka material UI is a React UI library that complements React for a fast build. It comes with pre-built React components and a default Material Styled Theme(which is very common in Google products). We can either keep the default them...

> Imported from [Hashnode](https://kir4n.hashnode.dev/mui-with-tailwind-css).

## What is Mui?

Mui aka material UI is a React UI library that complements React for a fast build. It comes with **pre-built React components** and a default **Material Styled Theme**(which is very common in Google products). We can either keep the default theme or override the default ones with ours. With the help of prebuilt components e.g input field, button etc it becomes very easy to build quick builds. Material UI also gives **Breakpoints** which is very helpful in making the website or web app responsive.

## What is tailwind css?

Tailwind CSS is a utility-first CSS framework for rapidly building custom user interfaces. It is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override

## Can we use Mui along with tailwind CSS?

Yes, you can! and the official [mui docs](https://mui.com/base/guides/working-with-tailwind-css) mentioned a way to customize mui unstyled base components using tailwind css, so you need to know what is an unstyled component and how they differ from mui core components let's check.

### **MUI Base vs Material UI**

> MUI Base features many of the same components as Material UI, but without the Material Design implementation.
>
> MUI Base is not packaged with any default theme or built-in style engine. This makes it a great choice if you need complete control over how your app's CSS is implemented.
>
> You could think of MUI Base as the "skeletal" or "headless" version of Material UI in fact, future versions of Material UI (starting with v6) will use these components and hooks for their foundational structure.

and now you are familiar with the unstyled base components but they are lacking some components from mui core package. If you want to proceed with base components then follow the [mui docs](https://mui.com/base/guides/working-with-tailwind-css) to customize with tailwind css else follow this...

## How to customize Mui core components with tailwind css?

For that you need to know how you can style a mui component, lets take an example of a mui button component.

The `Button` comes with three variants: text (default), contained and outlined. The variant props are used to define which variant you wanted. the default color applied on the button is blue by default so you want to customize the color...you can wrap the component with a theme provider and define the primary and secondary color but it takes some additional effort also, you try to style with sx props and you can do the same but need to control all button props classes like hover focus active and so on. You can even customize a button with makestyle hook from mui or using styled-components but this takes a lot of effort. Here comes the lifesaver tailwind CSS, with the help of tailwind CSS you can customize the button component with ease within inline and that's the specialty of tailwind CSS. you can just add a `classname="bg black text-white hover:bg-gray-300"` and so on... but to achieve this you need to configure some things to override default mui styles and base tailwind styles.

By installing mui and tailwindcss the mui css will be at the bottom of `<head>` tag in `index.html` if you look at from the browser console, so we need to inject mui css first so that tailwind can override styles.

For that, you can use injectfirst prop of `StyledEngineProvider` from mui and wrap this to the app component.

```javascript
//index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { StyledEngineProvider } from "@mui/material";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <StyledEngineProvider injectFirst>
      <App />
    </StyledEngineProvider>
  </React.StrictMode>
);
```

Alright, now you need to add `important:"#root"` in the config file of tailwind to make sure that tailwind can override other styles.

```javascript
//tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  important: "#root",
  theme: {
    fontFamily: {
      advent: ["Advent Pro", "sans-serif"],
    },
    extend: {},
  },
  plugins: [],
};
```

Also, you need to remove the `@tailwind base` style from the `index.css` file to clear the base styles from tailwind css.

Hurray! Now you got complete freedom with your design and the ability to create custom designs and components easily with Mui and Tailwind CSS.

Go checkout the codesandbox or [github](https://github.com/Kiranism/mui-with-tailwindcss) for the configuration code.

Thank you for Reading, If you found this useful feel free to like and connect with me on [**Linkedin**](https://www.linkedin.com/in/kiranism)

Happy Coding! 🥂

%\[[https://codesandbox.io/p/github/Kiranism/mui-with-tailwindcss/main](https://codesandbox.io/p/github/Kiranism/mui-with-tailwindcss/main)]


Last updated on December 20, 2022

---
title: "Things you should do before pushing React app to production"
description: "After all the hustles finally you are ready to push the react app to production and you wonder what are the things to take care before pushing local development code to a production server, lets check out
Environment variables
React imports environme..."
last_updated: "November 29, 2022"
source: "https://kiranism.dev/blog/things-you-should-do-before-pushing-react-app-to-production"
---

# Things you should do before pushing React app to production

After all the hustles finally you are ready to push the react app to production and you wonder what are the things to take care before pushing local development code to a production server, lets check out
Environment variables
React imports environme...

> Imported from [Hashnode](https://kir4n.hashnode.dev/things-you-should-do-before-pushing-react-app-to-production).

After all the hustles finally you are ready to push the react app to production and you wonder what are the things to take care before pushing local development code to a production server, lets check out

### Environment variables

React imports environment variables that are defined in a .env file at the root of the project. Im hoping you’re already familiar with the env configurations and all.

Let's say you're working on a large project that needs to have separate custom variables for development and production. For example, say you have a test API link for development and a live API link for production, and you don't want to manually change this in each file. So there is a built-in environment variable called NODE\_ENV. If you write process.env.NODE\_ENV and start the server npm start it will print the mode you are working in. Outputs development for npm start, production for npm build, and test for npm test.

Custom variables can be easily toggled, for example, by setting conditions for different modes using JavaScript's ternary operator or if-else statement.

Put this in your `.env` file

```
REACT_APP_DEV_MODE=This is development mode
REACT_APP_PRO_MODE=This is production mode
```

And put this in your `app.js` file.

```
{process.env.NODE_ENV === 'development' ? process.env.REACT_APP_DEV_MODE : process.env.REACT_APP_PRO_MODE}
```

If we run npm start it will print This is a development mode and if we run npm run build and then serve that build folder it will print This is a production mode.

### Console.log("you dont want me in production");

!\[3a25ok.jpg]\([https://cdn.hashnode.com/res/hashnode/image/upload/v1668759936816/gW24xtGqu.jpg](https://cdn.hashnode.com/res/hashnode/image/upload/v1668759936816/gW24xtGqu.jpg) align="center")
When you were developing your application, you probably placed many console.log statements throughout your code. It may display information that shouldn't be shown to the user, but most users (mostly) don't press her F12 key, so I consider it appropriate.

Not only is this a bad habit, you are lazy.
So make sure that you cleared all console.logs(), There are some other tricks to remove console.logs in production lets see!

Create a utility file `remove-console.js`

```javascript
// remove-console.js
 
export const GlobalDebug = (function () {
  var savedConsole = console;
  /**
   * @param {boolean} debugOn
   * @param {boolean} suppressAll
   */
  return function (debugOn, suppressAll) {
    var suppress = suppressAll || false;
    if (debugOn === false) {
      // supress the default console functionality
      console = {};
      console.log = function () {};
      // supress all type of consoles
      if (suppress) {
        console.info = function () {};
        console.warn = function () {};
        console.error = function () {};
      } else {
        console.info = savedConsole.info;
        console.warn = savedConsole.warn;
        console.error = savedConsole.error;
      }
    } else {
      console = savedConsole;
    }
  };
})();

```

Use this function at the root of your project or in any file you want to. `app.js`

```javascript
//app.js

import React, { useEffect, useState } from "react";
import { GlobalDebug } from "./helpers/remove-console";

// inside useEffect

useEffect(() => {
 // clear consoles in production and staging
    (process.env.NODE_ENV === "production" ||
      process.env.REACT_APP_ENV === "STAGING") &&
      GlobalDebug(false);
},[]) 

```

### React Developer Tools

React Developer Tools can be opened in any React app and it is useful while in the development stage.

So this makes sense in development, but what about in production? I'm asking this because technically any user can use devtools to sneak peak with the app.

Fortunately, there is a way to turn this off. Just override the devtools global hook before loading React.

```
// index.js

import { disableReactDevTools } from "@fvilers/disable-react-devtools";
// ...rest of imports //

if (process.env.NODE_ENV === "production") disableReactDevTools();
```

> You successfully disabled react devtools :)

!\[Before.png]\([https://cdn.hashnode.com/res/hashnode/image/upload/v1669721570428/kUQcPT1YGH.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1669721570428/kUQcPT1YGH.png) align="center")

### Redux DevTools

Redux DevTools is a handy browser extension that helps React developers. Available in Chrome, Edge, and Firefox, it's very useful for debugging React application state.

But you don't need to expose your app state to your users which contains some logical informations.

so lets check how to disable Redux devtools in production mode

```
// index.js

const composeEnhancers =
  (process.env.NODE_ENV !== "production" &&
    typeof window !== "undefined" &&
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
  compose;
const store = createStore(
  persistedReducer,
  composeEnhancers(applyMiddleware(reduxThunk))
);

```

> You successfully disabled redux devtools :)

!\[Before (1).png]\([https://cdn.hashnode.com/res/hashnode/image/upload/v1669721908734/V5MTGqm9M.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1669721908734/V5MTGqm9M.png) align="center")

Thank you for Reading, If you learned anything new today feel free to like and connect with me on  [Linkedin](https://www.linkedin.com/in/kiranism)

Happy Coding! 🥂


Last updated on November 29, 2022