When I set out to build PickBlend, I gave myself one
hard constraint that shaped every decision after it:
your text must never leave your browser.
Not "we promise not to look." Not "we delete it after
30 days." Literally never sent anywhere. No server
receives it, because there is no server processing
text at all.
This post is a deep dive into how I built
PickBlend — 11 free,
privacy-first writing tools — using Next.js 16,
TypeScript and Tailwind CSS, with zero backend, zero
database and zero API cost. If you are building
client-side tools, static sites, or just care about
privacy-respecting architecture, there is something
here for you.
The Core Constraint: Everything Runs Client-Side
Most "free" online tools follow a familiar pattern.
You paste your text, it gets POSTed to a server, the
server processes it, and the result comes back. Somewhere
in that round trip your data is logged, analyzed, or
used to train something.
I wanted the opposite. Every tool on PickBlend is a
React client component that runs entirely in the
browser. The word count, the readability score, the
keyword density — all of it computes on your device
using plain JavaScript. There is no fetch() call when
you use a tool. You can open DevTools, go to the
Network tab, type into any tool, and watch it stay
completely empty.
This is not just a privacy talking point. It has real
architectural consequences:
- No backend to maintain or pay for
- No database to secure
- No cold starts or server latency
- Sub-100ms interactions because there is no network hop
- Tools keep working offline once the page loads
The Stack
Here is what powers the whole thing:
- Framework: Next.js 16 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS with a custom design-token system
- Hosting: Vercel (static generation + global CDN)
- Persistence: localStorage (no database)
- Contact form: Resend (the only server touch, and only when you deliberately submit a message)
Every tool page is statically generated. When Googlebot
or a user hits a page, they get complete HTML on the
first request — no client-side rendering required for
content. This matters enormously for SEO, which I will
come back to.
Building the Tools: The Interesting Problems
Word Counter — the deceptively hard one
Counting words sounds trivial until you handle real
input. Splitting on whitespace is the naive approach
and it breaks immediately on hyphenated words,
contractions, and stray punctuation.
The keyword-density feature was the fun part. It needs
to strip punctuation, normalize case, filter stop
words, and compute frequency percentages — all under a
millisecond on every keystroke so the UI never stutters.
function getKeywordDensity(text) {
const words = text
.toLowerCase()
.replace(/[^a-z\s]/g, "")
.split(/\s+/)
.filter(w => w.length > 2 && !STOP_WORDS.has(w));
const freq = new Map();
for (const w of words) {
freq.set(w, (freq.get(w) ?? 0) + 1);
}
return freq;
}
Using a Map instead of a plain object avoids prototype
pollution edge cases and is measurably faster for the
frequent get/set pattern.
Readability Score — six formulas, one hard dependency
The readability checker
runs six formulas at once: Flesch Reading Ease,
Flesch-Kincaid Grade Level, Gunning Fog, SMOG, ARI and
Coleman-Liau. Five of the six depend on one genuinely
hard problem: counting syllables in English, a language
famous for ignoring its own rules.
There is no perfect client-side syllable counter, but a
vowel-group approach with corrections for silent-e and
common suffixes gets you close enough for readability
scoring:
function countSyllables(word) {
word = word.toLowerCase().replace(/[^a-z]/g, "");
if (word.length <= 3) return 1;
word = word.replace(/e$/, "");
const groups = word.match(/[aeiouy]+/g);
let count = groups ? groups.length : 1;
if (word.endsWith("le") && word.length > 2) count++;
if (word.endsWith("ed") && !word.match(/[td]ed$/)) count--;
return Math.max(1, count);
}
Text Case Converter — normalize first, then transform
The case converter
handles 10 formats: UPPERCASE, lowercase, Title Case,
Sentence case, camelCase, PascalCase, snake_case,
kebab-case, SCREAMING_SNAKE_CASE and alternating case.
The trick that makes it robust is normalizing any input
— whatever case it arrives in — down to an array of
lowercase words first, then building the target format
from that clean base:
function normalizeWords(text) {
return text
.replace(/([a-z])([A-Z])/g, "$1 $2")
.replace(/[_\-]+/g, " ")
.toLowerCase()
.split(/\s+/)
.filter(Boolean);
}
This means it correctly converts snake_case to camelCase,
kebab-case to PascalCase, or any other combination,
without special-casing each pair.
Text to Speech — the browser already has an engine
This is my favorite because it costs nothing and most
people do not know it exists. The
text to speech tool
uses the native Web Speech API — window.speechSynthesis
— which ships in every modern browser.
const utterance = new SpeechSynthesisUtterance(text);
utterance.voice = selectedVoice;
utterance.rate = rate;
utterance.pitch = pitch;
window.speechSynthesis.speak(utterance);
No API key. No per-character billing. No text sent to a
cloud voice service. The voices come from the user's own
operating system.
The one gotcha: voices load asynchronously and
getVoices() often returns an empty array on first call.
You have to listen for the onvoiceschanged event:
speechSynthesis.onvoiceschanged = () => {
setVoices(speechSynthesis.getVoices());
};
Also remember to call speechSynthesis.cancel() on
component unmount, or speech keeps playing after the
user navigates away.
The SEO Architecture
A privacy tool that nobody can find is a tree falling in
an empty forest. Because every page is statically
generated, Google receives full HTML with all content
present — a big advantage over client-rendered
competitors whose content only appears after JavaScript
executes.
On top of that, every page ships:
- Unique title tags and meta descriptions
- JSON-LD structured data: WebApplication, BreadcrumbList, FAQPage and HowTo schemas
- 800+ words of genuinely useful content per tool
- Internal links forming a tight topic cluster
- Open Graph images for social sharing
The FAQPage schema is the highest-leverage piece. It
makes pages eligible for FAQ rich results and
People-Also-Ask boxes, which expand your footprint in
search results even before you crack page one.
What I Learned
Static generation is underrated. Eleven tools, five blog
posts, legal pages — all static HTML served from a CDN.
No server, no scaling worries, no bill that grows with
traffic.
localStorage is enough for most tools. Developers reach
for a database by reflex. For tools that process text
locally, localStorage handles persistence with zero
infrastructure. Every tool auto-saves your work so you
can close the tab and come back to it.
Design tokens pay off on day one. Setting up CSS
variables for color, spacing and typography before
building the first tool meant all 11 tools look
consistent with no extra effort per tool.
The browser is more capable than we give it credit for.
Speech synthesis, localStorage, the full text-processing
power of JavaScript — a huge amount of "needs a backend"
functionality actually does not.
The Full Toolkit
All 11 tools, all free, all client-side:
- Word Counter
- Reading Time Calculator
- Character Counter
- Text Case Converter
- Lorem Ipsum Generator
- Sentence Counter
- Text Repeater
- Readability Score Checker
- Word Frequency Counter
- Paragraph Counter
- Text to Speech
You can try them all at pickblend.com.
No sign-up, no tracking, and your text genuinely never
leaves your browser.
If you are building client-side tools and want to
compare notes on syllable counting, case normalization,
or wrangling the Web Speech API, drop a comment. Always
happy to talk shop.
What "needs a backend" feature have you managed to move
fully client-side? I would love to hear it.

Top comments (0)