<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Jaspal Writes]]></title><description><![CDATA[Jaspal Writes]]></description><link>https://js91872.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Jaspal Writes</title><link>https://js91872.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 15 Sep 2026 19:16:24 GMT</lastBuildDate><atom:link href="https://js91872.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[What I Learned Building 170+ Browser-Based Tools with Next.js]]></title><description><![CDATA[Building one online calculator is easy.
Building a platform containing more than a hundred calculators and utilities is a completely different engineering problem.
When I started building Navorika, my]]></description><link>https://js91872.hashnode.dev/what-i-learned-building-170-browser-based-tools-with-next-js</link><guid isPermaLink="true">https://js91872.hashnode.dev/what-i-learned-building-170-browser-based-tools-with-next-js</guid><category><![CDATA[Next.js]]></category><category><![CDATA[codex]]></category><dc:creator><![CDATA[js91872]]></dc:creator><pubDate>Sun, 30 Aug 2026 09:33:38 GMT</pubDate><content:encoded><![CDATA[<p>Building one online calculator is easy.</p>
<p>Building a platform containing more than a hundred calculators and utilities is a completely different engineering problem.</p>
<p>When I started building Navorika, my original idea was straightforward: create useful online tools that load quickly, don't require an account, and process data locally in the browser whenever possible.</p>
<p>The first few tools were simple.</p>
<p>Then the project grew.</p>
<p>Finance calculators were followed by PDF utilities, image tools, developer utilities, construction calculators, networking tools, and other everyday calculators.</p>
<p>At that point, I discovered something important:</p>
<p>«The difficult part isn't building the 10th tool. It's designing a system that can still make sense when you build the 100th.»</p>
<p>This article covers some of the lessons I've learned while building the platform with Next.js and TypeScript, particularly around architecture, browser-side processing, validation, privacy, routing, and scaling a large collection of relatively small applications.</p>
<p>1. A Tool Is More Than a React Component</p>
<p>My early mental model was roughly:</p>
<p>/tool</p>
<p>├── title</p>
<p>├── description</p>
<p>├── form</p>
<p>├── calculation</p>
<p>└── result</p>
<p>That works when you have five tools.</p>
<p>It becomes painful when you have 100+.</p>
<p>Every tool actually has several responsibilities:</p>
<p>Tool</p>
<p>├── Metadata</p>
<p>├── Category</p>
<p>├── Route</p>
<p>├── UI</p>
<p>├── Input validation</p>
<p>├── Processing logic</p>
<p>├── Result formatting</p>
<p>├── Explanatory content</p>
<p>├── Related tools</p>
<p>├── Structured data</p>
<p>└── Search metadata</p>
<p>If each page implements these independently, inconsistencies appear very quickly.</p>
<p>One page uses one breadcrumb structure.</p>
<p>Another uses a different heading hierarchy.</p>
<p>One calculator validates negative numbers.</p>
<p>Another doesn't.</p>
<p>One page has related tools.</p>
<p>Another forgets them.</p>
<p>The solution for me was to stop thinking about the project as a collection of pages and start thinking about it as a registry of applications.</p>
<p>Conceptually, a tool can be represented like this:</p>
<p>interface Tool {</p>
<p>slug: string;</p>
<p>name: string;</p>
<p>description: string;</p>
<p>category: string;</p>
<p>keywords: string[];</p>
<p>relatedTools?: string[];</p>
<p>}</p>
<p>The actual architecture can contain much more information, but the important idea is that the tool's identity lives in structured data rather than being recreated independently across multiple parts of the application.</p>
<p>Once this becomes the source of truth, many things can be generated from it.</p>
<p>Registry</p>
<p>│</p>
<p>├── Tool routes</p>
<p>├── Category pages</p>
<p>├── Search</p>
<p>├── Related tools</p>
<p>├── Sitemap</p>
<p>├── Navigation</p>
<p>└── Discovery pages</p>
<p>This was probably the biggest architectural lesson from scaling the project.</p>
<p>2. Browser-Side Processing Is Surprisingly Powerful</p>
<p>Another decision that influenced the architecture was trying to process information locally whenever the task didn't genuinely require a server.</p>
<p>Consider JSON formatting.</p>
<p>A basic formatter doesn't need an API.</p>
<p>The browser already provides what we need:</p>
<p>const parsed = JSON.parse(input);</p>
<p>const formatted = JSON.stringify(parsed, null, 2);</p>
<p>That means the workflow can simply be:</p>
<p>User input</p>
<p>↓</p>
<p>Browser</p>
<p>↓</p>
<p>JavaScript parser</p>
<p>↓</p>
<p>Result</p>
<p>instead of:</p>
<p>User input</p>
<p>↓</p>
<p>HTTP request</p>
<p>↓</p>
<p>Application server</p>
<p>↓</p>
<p>Processing</p>
<p>↓</p>
<p>HTTP response</p>
<p>↓</p>
<p>Result</p>
<p>The first architecture has several advantages.</p>
<p>There is no network round trip for the calculation.</p>
<p>The server doesn't need to process the user's data.</p>
<p>The infrastructure has less work to do. And for many tools, the user's working data never needs to leave the device.</p>
<p>I implemented this approach in tools such as the JSON formatter on "Navorika" (<a href="https://navorika.com/tools/universal-json-studio">https://navorika.com/tools/universal-json-studio</a>), where parsing and formatting happen in the browser.</p>
<p>The same principle applies to many small utilities:</p>
<p>- Base64 encoding</p>
<p>- URL encoding</p>
<p>- UUID generation</p>
<p>- timestamp conversion</p>
<p>- JSON comparison</p>
<p>- CSV conversion</p>
<p>- image manipulation</p>
<p>- certain PDF operations</p>
<p>- text transformations</p>
<p>- subnet calculations</p>
<p>This doesn't mean every application should be client-side. It means we should ask a useful question before creating an API:</p>
<p>«Does this operation actually require a server?»</p>
<p>Often, the answer is no.</p>
<p>3. Privacy Can Be an Architectural Property</p>
<p>Privacy statements are often treated as website copy.</p>
<p>But while building browser utilities, I started thinking about privacy differently.</p>
<p>It can be an architectural consequence.</p>
<p>Suppose someone wants to format this:</p>
<p>{</p>
<p>"customer": "example",</p>
<p>"order": 1428,</p>
<p>"status": "pending"</p>
<p>}</p>
<p>If the formatter sends the JSON to a server, the application operator potentially receives that information.</p>
<p>If "JSON.parse()" and "JSON.stringify()" run locally, the application doesn't need to receive it at all.</p>
<p>That's a meaningful distinction.</p>
<p>The same reasoning becomes more interesting with tokens.</p>
<p>A JWT typically contains three dot-separated components:</p>
<p>header.payload.signature</p>
<p>The header and payload use Base64URL encoding.</p>
<p>You don't need to send a token to a server simply to inspect those encoded components.</p>
<p>The browser can decode them locally.</p>
<p>But there is an important security distinction here.</p>
<p>4. JWT Decoding Is Not JWT Verification</p>
<p>This deserves special attention because it's an easy mistake to make when building developer tools.</p>
<p>Decoding a JWT answers:</p>
<p>«What does this token contain?»</p>
<p>Verification answers:</p>
<p>«Should I trust this token?»</p>
<p>Those are not the same question.</p>
<p>You can decode:</p>
<p>xxxxx.yyyyy.zzzzz</p>
<p>and display something resembling:</p>
<p>{</p>
<p>"sub": "123",</p>
<p>"role": "admin"</p>
<p>}</p>
<p>That does not establish that the token is authentic.</p>
<p>The signature still needs to be verified using the appropriate cryptographic key and algorithm, and claims such as expiration, issuer and audience may need validation depending on the application.</p>
<p>A utility should therefore never imply:</p>
<p>Decoded successfully = valid token</p>
<p>The correct relationship is:</p>
<p>Decode</p>
<p>↓</p>
<p>Inspect structure</p>
<p>Verify</p>
<p>↓</p>
<p>Establish authenticity</p>
<p>↓</p>
<p>Validate relevant claims</p>
<p>This influenced how I designed the JWT utility in my developer-tool collection: decoding is explicitly presented as inspection rather than signature verification.</p>
<p>It also taught me a broader lesson.</p>
<p>A good developer tool needs to explain what its result does not mean.</p>
<p>5. Validation Matters More Than the Formula</p>
<p>Calculators often look deceptively simple.</p>
<p>Take a basic formula:</p>
<p>const result = width * length * depth;</p>
<p>Writing that line is trivial.</p>
<p>The real work is everything around it.</p>
<p>What happens when:</p>
<p>width = -5</p>
<p>length = 0</p>
<p>depth = "hello"</p>
<p>What units are being used?</p>
<p>Can the user enter decimals?</p>
<p>What is the sensible maximum?</p>
<p>What should happen when a required field is empty?</p>
<p>Should zero be valid?</p>
<p>Does the output require rounding?</p>
<p>A production calculator therefore looks more like:</p>
<p>Input</p>
<p>↓</p>
<p>Type validation</p>
<p>↓</p>
<p>Range validation</p>
<p>↓</p>
<p>Unit normalization</p>
<p>↓</p>
<p>Calculation</p>
<p>↓</p>
<p>Precision handling</p>
<p>↓</p>
<p>Result</p>
<p>↓</p>
<p>Explanation</p>
<p>As the number of tools increased, validation became one of the biggest quality differences between something that merely looks like a calculator and something that is actually useful.</p>
<p>6. Some Tools Should Not Be Published Yet</p>
<p>This was a harder lesson.</p>
<p>When building a large platform, there is a temptation to increase the tool count quickly.</p>
<p>But a page with a button that produces unreliable output isn't a tool.</p>
<p>It's technical debt with a URL.</p>
<p>For example, imagine creating a code formatter.</p>
<p>A simplistic implementation might manipulate whitespace using regular expressions.</p>
<p>That may work on trivial input.</p>
<p>Then someone gives it:</p>
<p>const message = "hello world";</p>
<p>An aggressive whitespace transformation could modify the string itself.</p>
<p>The interface still looks correct.</p>
<p>The button still works.</p>
<p>The output is simply wrong.</p>
<p>Parser-dependent tasks should use appropriate parsers.</p>
<p>That led me to adopt a simple principle:</p>
<p>«If the correct processing engine isn't ready, temporarily disabling the functionality is better than pretending it works.»</p>
<p>This sounds obvious, but it becomes increasingly important when the number of applications grows.</p>
<p>7. Tool Relationships Become a Graph</p>
<p>Once I had dozens of utilities, another problem appeared.</p>
<p>Categories weren't enough.</p>
<p>Consider JSON.</p>
<p>A developer might start with:</p>
<p>JSON Formatter</p>
<p>and then need:</p>
<p>JSON Schema Validator</p>
<p>or:</p>
<p>JSON → CSV</p>
<p>or:</p>
<p>JSON Diff</p>
<p>These tools belong to the same category, but more importantly they belong to the same workflow.</p>
<p>Networking provides another example.</p>
<p>CIDR Calculator</p>
<p>│</p>
<p>├── IP Range Calculator</p>
<p>├── VLSM Calculator</p>
<p>└── MAC Generator</p>
<p>This led me to think about tools as nodes in a graph.</p>
<p>JSON Formatter</p>
<p>/ \</p>
<p>/ \</p>
<p>JSON Schema JSON Diff</p>
<p>\ /</p>
<p>\ /</p>
<p>JSON → CSV</p>
<p>The relationship between applications can be as important as the category they belong to.</p>
<p>Eventually I created workflow-oriented collections, including a "web developer toolkit" (<a href="https://navorika.com/toolkits/web-developer-tools">https://navorika.com/toolkits/web-developer-tools</a>), rather than relying exclusively on broad category pages.</p>
<p>This has benefits beyond navigation.</p>
<p>It helps answer the question:</p>
<p>«What is the user likely to need next?»</p>
<p>That's a much more useful question than:</p>
<p>«What other pages can I link here?»</p>
<p>8. Programmatic Pages Need Guardrails</p>
<p>When working with many routes, automation becomes attractive.</p>
<p>A registry can generate:</p>
<p>routes</p>
<p>metadata</p>
<p>sitemaps</p>
<p>breadcrumbs</p>
<p>category pages</p>
<p>related tools</p>
<p>structured data</p>
<p>That's useful.</p>
<p>But automation also makes it extremely easy to create low-quality pages at scale.</p>
<p>Suppose you have 100 tools and automatically generate 100 descriptions like:</p>
<p>Use our X calculator to calculate X quickly and easily.</p>
<p>Technically, you now have 100 pages.</p>
<p>Practically, you have created almost no useful information.</p>
<p>I found it more useful to think of each tool page as answering several questions:</p>
<p>What does this tool do?</p>
<p>How does it work?</p>
<p>What assumptions does it make?</p>
<p>How should I use it?</p>
<p>What does the result mean?</p>
<p>What are its limitations?</p>
<p>What should I use next?</p>
<p>A calculator result without context can be misleading.</p>
<p>This is especially important for financial, health, construction and technical calculations.</p>
<p>9. Build-Time Validation Becomes Essential</p>
<p>Humans are bad at manually maintaining large registries.</p>
<p>Eventually you will:</p>
<p>- add a registry entry without a route</p>
<p>- create a route without registering it</p>
<p>- reference a nonexistent related tool</p>
<p>- duplicate a slug</p>
<p>- forget metadata</p>
<p>- break a category relationship</p>
<p>Instead of relying entirely on code review, I started treating architecture rules as things that could be tested.</p>
<p>Conceptually:</p>
<p>for (const tool of registry) {</p>
<p>assert(routeExists(tool.slug));</p>
<p>assert(validCategory(tool.category));</p>
<p>assert(uniqueSlug(tool.slug));</p>
<p>}</p>
<p>You can go further:</p>
<p>assert(allRoutesAreRegistered());</p>
<p>assert(allRelatedToolsExist());</p>
<p>assert(allCategoriesAreValid());</p>
<p>assert(noDuplicateSlugs());</p>
<p>Then the deployment pipeline becomes something like:</p>
<p>npm run typecheck</p>
<p>npm run validate:architecture</p>
<p>npm run lint</p>
<p>npm run build</p>
<p>This changes architectural conventions from documentation into executable rules.</p>
<p>That's incredibly useful.</p>
<p>Documentation says:</p>
<p>«Please remember to register every tool.»</p>
<p>Validation says:</p>
<p>ERROR: Route exists but registry entry is missing.</p>
<p>I prefer the second.</p>
<p>10. Static Generation Has Been a Good Fit</p>
<p>Many online utilities don't need server rendering for every request.</p>
<p>The application shell, explanation, metadata and UI can often be generated ahead of time.</p>
<p>Then interactive calculations happen in the browser.</p>
<p>Conceptually:</p>
<p>BUILD TIME</p>
<p>↓</p>
<p>Generate tool pages</p>
<p>↓</p>
<p>Static HTML/CSS/JS</p>
<p>↓</p>
<p>Serve page</p>
<p>↓</p>
<p>Browser executes interactive tool</p>
<p>For a platform containing many small utilities, this is an attractive model.</p>
<p>The server primarily delivers the application.</p>
<p>The browser handles much of the interaction.</p>
<p>Of course, some tools genuinely need external data.</p>
<p>Exchange rates are an obvious example.</p>
<p>Current pricing data is another.</p>
<p>The architectural rule shouldn't be:</p>
<p>«Everything must be local.»</p>
<p>It should be:</p>
<p>«Don't introduce server processing unless the functionality requires it.»</p>
<p>11. Reusable UI Is Necessary, but Reusable Logic Is More Important</p>
<p>Component reuse is obvious in React.</p>
<p>You create reusable elements such as:</p>
<p>ToolHeader</p>
<p>InputField</p>
<p>ResultCard</p>
<p>FAQ</p>
<p>RelatedTools</p>
<p>Breadcrumb</p>
<p>But the more important reuse is often behavioral.</p>
<p>For example:</p>
<p>function parsePositiveNumber(value: string) {</p>
<p>const number = Number(value);</p>
<p>if (!Number.isFinite(number) || number &lt;= 0) {</p>
<p>return null;</p>
<p>}</p>
<p>return number;</p>
<p>}</p>
<p>Centralizing common behavior prevents slightly different validation rules from appearing everywhere.</p>
<p>The same applies to:</p>
<p>- number formatting</p>
<p>- percentage parsing</p>
<p>- unit conversions</p>
<p>- file-size validation</p>
<p>- error states</p>
<p>- clipboard behavior</p>
<p>- download helpers</p>
<p>- metadata generation</p>
<p>Consistency becomes more valuable as the application grows.</p>
<p>12. Don't Force Everything Into One Mega-Component</p>
<p>The opposite problem also exists.</p>
<p>Once developers discover reusable components, it's tempting to build:</p>
<p>&lt;UniversalCalculator</p>
<p>formula="..."</p>
<p>inputs={[...]}</p>
<p>output="..."</p>
<p>/&gt;</p>
<p>and force every calculator through it.</p>
<p>That works until the tools become meaningfully different.</p>
<p>A mortgage calculator doesn't behave like a Base64 encoder.</p>
<p>A subnet calculator doesn't behave like an image cropper.</p>
<p>A PDF merger doesn't behave like a construction-material estimator.</p>
<p>Reuse infrastructure.</p>
<p>Reuse patterns.</p>
<p>Reuse validation.</p>
<p>But allow individual tools to own their domain-specific behavior.</p>
<p>I've found that balance much easier to maintain.</p>
<p>13. URLs Become Infrastructure</p>
<p>When you have only a few pages, changing a URL seems harmless.</p>
<p>At scale, URLs become part of the architecture.</p>
<p>Other pages reference them.</p>
<p>Search engines discover them.</p>
<p>Users bookmark them.</p>
<p>External websites may link to them.</p>
<p>A slug such as:</p>
<p>/tools/cidr-subnet-calculator</p>
<p>is no longer merely a filename.</p>
<p>It's a public interface.</p>
<p>This means changing slugs casually becomes similar to changing an API contract.</p>
<p>Stable URLs are worth protecting.</p>
<p>14. Tool Count Is a Bad Success Metric</p>
<p>When I began scaling the project, the number of tools naturally felt important.</p>
<p>50 tools.</p>
<p>100 tools.</p>
<p>150 tools.</p>
<p>But the number itself becomes increasingly meaningless.</p>
<p>I'd rather have:</p>
<p>100 dependable tools</p>
<p>than:</p>
<p>500 pages containing weak implementations</p>
<p>A better set of engineering questions is:</p>
<p>Does the calculation work?</p>
<p>Are inputs validated?</p>
<p>Are limitations explained?</p>
<p>Does it work on mobile?</p>
<p>Is sensitive data unnecessarily transmitted?</p>
<p>Can users understand the result?</p>
<p>Does the related workflow make sense?</p>
<p>Those are harder metrics.</p>
<p>They're also much more useful.</p>
<p>15. What I'd Do Differently If I Started Again</p>
<p>If I were starting the project today, I would establish the registry architecture almost immediately.</p>
<p>My order would probably be:</p>
<p>1. Define the tool schema</p>
<p>2. Define categories</p>
<p>3. Build reusable page primitives</p>
<p>4. Create shared validation helpers</p>
<p>5. Establish URL conventions</p>
<p>6. Add architecture validation</p>
<p>7. Build 10 excellent tools</p>
<p>8. Observe usage</p>
<p>9. Create workflow relationships</p>
<p>10. Scale gradually</p>
<p>Instead, like many side projects, some of the architecture emerged only after the application became complicated enough to require it.</p>
<p>That's not necessarily bad.</p>
<p>Premature architecture can be just as harmful as insufficient architecture.</p>
<p>But once repeated patterns become obvious, formalizing them pays off quickly.</p>
<p>Final Thoughts</p>
<p>Building a large collection of small browser applications has changed how I think about web tools.</p>
<p>The calculation itself is often the easiest part.</p>
<p>The harder problems are:</p>
<p>- architecture</p>
<p>- validation</p>
<p>- privacy</p>
<p>- discoverability</p>
<p>- consistency</p>
<p>- maintainability</p>
<p>- explaining limitations</p>
<p>- deciding when not to ship something</p>
<p>Next.js and modern browser APIs make it surprisingly practical to build useful applications where much of the actual work happens on the user's device.</p>
<p>But scaling from a handful of utilities to hundreds requires treating the project as a system rather than a folder full of pages.</p>
<p>That's what I'm currently experimenting with while building "Navorika" (<a href="https://navorika.com/">https://navorika.com/</a>).</p>
<p>And the biggest lesson so far is probably the simplest:</p>
<p>Don't optimize for how many tools you can publish. Optimize for how many problems you can solve correctly.</p>
]]></content:encoded></item></channel></rss>