No React. No Next.js. No build step. No node_modules.
I built TextKit — a collection of 37 free online tools for text processing, data cleaning, and developer utilities — using Flask, Tailwind CSS via CDN, and vanilla JavaScript. The entire project starts with python app.py.
Here's the stack, the architecture decisions, and what I've learned.
The stack
Python + Flask → serves HTML via Jinja2 templates
Tailwind CSS CDN → styling, zero build config
Vanilla JavaScript → all tool logic, 100% client-side
Markdown + YAML → blog system
Render → hosting
Cloudflare → DNS + caching
Umami → privacy-friendly analytics
No database. No auth system. No npm. The entire deployment is a Flask app serving static-ish pages.
Why no framework?
Because these tools don't need one.
A JSON formatter takes text input, calls JSON.parse() and JSON.stringify(), and displays the result. A password generator calls crypto.getRandomValues() and builds a string. A diff checker runs an LCS algorithm on two arrays of strings.
Every single tool follows the same pattern: input → transform → output. A <textarea>, an addEventListener('input', ...), and a function. React adds a virtual DOM, a component lifecycle, a build system, and 40KB of JavaScript to do the same thing that 20 lines of vanilla JS handles.
I'm not anti-framework. I use React at work. But for this project, a framework would have been overhead with zero user benefit.
Architecture
Every tool is a Jinja2 template that extends base.html:
templates/
base.html → nav, footer, fonts, theme CSS
index.html → homepage with search + tool grid
tools/
json-formatter.html → extends base.html
password-generator.html → extends base.html
regex-tester.html → extends base.html
... (37 tools)
The Flask app is dead simple:
@app.route('/json-formatter')
def json_formatter():
return render_template('tools/json-formatter.html')
One route per tool. The TOOLS registry is a list of dicts that drives the homepage grid, sitemap, and navigation. Add a tool to the list, it shows up everywhere.
All logic is client-side
Hard rule from day one: no text data goes to the server. Ever.
Users paste sensitive stuff into text tools — API keys, database queries, internal emails, customer data. If any of that hits a server, you've created a trust and liability problem. Everything runs in the browser, and users can verify this by disconnecting from the internet — every tool still works.
For most tools this is trivial. But a few pushed the limits:
Diff Checker — implements a full Longest Common Subsequence algorithm with character-level highlighting within changed lines. The LCS matrix is O(m×n) which means comparing two 5,000-line texts creates a 25 million cell array. Works, but I added a warning for large inputs.
Regex Tester — runs user-supplied patterns via new RegExp(). Zero-length matches like \b cause infinite loops in the exec() loop if you don't handle them:
while ((match = regex.exec(text)) !== null) {
matches.push(match);
if (match[0].length === 0) regex.lastIndex++; // prevents infinite loop
}
CSV Parser — you might think parsing CSV is text.split(','). It's not. Real-world CSV has quoted fields with commas inside, escaped quotes (double-quote within a quoted field), and newlines within quoted fields. The parser is a proper state machine.
Time Zone Converter — uses the browser's Intl.DateTimeFormat API for all timezone calculations. No timezone library, no database file. Modern browsers know every IANA timezone and handle DST automatically.
The tool list
37 tools across 9 categories:
Delimiter tools (10): Column↔Comma, Column↔Tab, Column↔Semicolon, Column↔Pipe, Column↔Space — all bidirectional
Text transform (5): Case Converter, Trim Whitespace, Add Prefix/Suffix, Number Lines, Slug Generator
Data cleaning (3): Remove Duplicates, Remove Blank Lines, Sort Lines
Text analysis (2): Word Counter, Count Lines
Text extraction (4): Extract Emails, URLs, Numbers, Phone Numbers
Generators (3): Password Generator, UUID Generator, Lorem Ipsum Generator
Developer tools (5): JSON Formatter, Regex Tester, Diff Checker, Epoch Converter, Time Zone Converter
Encode/Decode (3): Base64, URL Encode/Decode, Text to Binary
Data formats (2): CSV to JSON, JSON to CSV
SEO as architecture
Each tool page has:
- Unique title and meta description targeting specific search keywords
- 200+ word "what is this tool" section
- FAQ section with
<details>/<summary>tags - FAQPage schema markup (JSON-LD) — earns rich snippets in Google
- WebApplication schema
- Related tools section with internal cross-links
- Canonical URL and OpenGraph tags
The SEO content is roughly 70% of each page. The tool itself is a textarea and some buttons. Google ranks pages for informational queries, and the tool functionality keeps users engaged and builds backlinks organically.
I also run a blog with Markdown files parsed via a simple Flask route. Each post targets a "how to" keyword and links to the relevant tool. The blog posts rank for informational queries, drive traffic, and pass internal link authority to the tool pages.
What I'd do differently
Build the high-traffic tools first. I started with delimiter converters because they were simple. But JSON Formatter (350K monthly searches) and Password Generator (450K monthly searches) should have been first — more search volume, earlier indexing.
Don't use Tailwind CDN in production. It works and it's zero-config, but it loads the entire Tailwind stylesheet (~300KB). Self-hosting a purged CSS file would be much smaller. Haven't gotten around to it.
SEO content takes longer than building tools. Writing the "what is" sections, FAQs, and schema markup for 37 tools took more hours than writing the JavaScript. Plan for that.
Current status
- 37 tools live
- 12 blog posts
- Indexed and ranking in Google (averaging position 20 and climbing)
- Thermal receipt theme — because developer tools should have personality
Everything is free, no accounts, no tracking. Check it out: textkit.dev
Happy to answer questions about the stack, the SEO approach, or any of the tool implementations.
Top comments (0)