How I Built 30 Privacy-First Dev Tools with Blazor WebAssembly (and Why Nothing Leaves Your Browser)
By Sarfaraz Khan ·
Every developer has done this at least once: you have a JWT from production, you need to see what's inside it, so you paste it into the first "JWT decoder" Google gives you.
That token might contain a user ID, an email, roles, maybe a tenant name. And you just sent it to a server you know nothing about.
Same story with JSON formatters, diff checkers, Base64 decoders, .env validators. We paste API keys, customer data and config files into random websites without thinking about it, because the tools are convenient and we're in a hurry.
So I built SamToolkit: a set of developer utilities where the processing happens in your browser. No upload, no backend call, no logging your input. This post is about how I built it with Blazor WebAssembly, what worked, and what hurt.
Why Blazor WebAssembly?
The honest answer: I'm a C# developer, and I wanted to ship fast in the language I think in.
But it also turned out to be a good fit for this kind of product:
- The whole app runs client-side. Blazor WASM downloads the .NET runtime and your compiled app into the browser. Once loaded, the tools work without talking to any server. That's exactly the privacy guarantee I wanted, enforced by the architecture instead of a promise in a privacy policy.
- The .NET base class library does a lot of heavy lifting.
System.Text.Json,Convert.ToBase64String,Regex,DateTimeOffset,Uri, string manipulation — most "dev tool" logic is already in the framework. - Static hosting. The output is just static files. No server to maintain, patch or pay for at scale.
- Offline-capable. After the first load, most tools keep working even if your connection drops.
The architecture (it's boring on purpose)
Every tool follows the same pattern:
/Pages/Tools/JwtDecoder.razor → UI + route
/Services/JwtService.cs → pure logic, no UI, no I/O
/Shared/ToolLayout.razor → common header, SEO tags, related tools
Keeping the logic in plain C# classes with no dependencies on Blazor means it's trivially unit-testable and reusable. A simplified JWT decoder service looks like this:
public static class JwtService
{
public static (string Header, string Payload) Decode(string token)
{
var parts = token.Trim().Split('.');
if (parts.Length < 2)
throw new FormatException("A JWT needs at least a header and a payload.");
return (Pretty(DecodePart(parts[0])), Pretty(DecodePart(parts[1])));
}
private static string DecodePart(string base64Url)
{
var s = base64Url.Replace('-', '+').Replace('_', '/');
s = s.PadRight(s.Length + (4 - s.Length % 4) % 4, '=');
return Encoding.UTF8.GetString(Convert.FromBase64String(s));
}
private static string Pretty(string json) =>
JsonSerializer.Serialize(
JsonDocument.Parse(json).RootElement,
new JsonSerializerOptions { WriteIndented = true });
}
No network call anywhere. You can open DevTools → Network while using it and watch nothing happen. That's the whole point.
Lesson 1: The initial download is the real cost
Blazor WASM's biggest trade-off is first load. You're shipping a runtime to the browser. For a tool site, where someone lands from Google wanting an answer now, that matters.
What helped:
- Trimming (
PublishTrimmed) to strip unused framework code. - Compression. Serve the Brotli-compressed files; the difference is large.
- Keeping third-party packages to a minimum. Every NuGet package is bytes your visitor downloads. I rewrote small helpers instead of pulling in libraries for them.
- A useful loading state. Instead of the default "Loading...", show the page title and what the tool does, so the user isn't staring at a blank screen.
After the first visit, the files are cached and every other tool opens instantly — which is great for people who come back.
Lesson 2: SEO for a single-page app takes deliberate work
This is where Blazor WASM fights you. Out of the box, search engines see an almost-empty HTML shell.
Things that made a real difference:
- One tool per route. I originally combined related tools on one page. Splitting them into dedicated URLs (
/tools/jwt-decoder,/tools/diff-checker,/tools/case-converter) matched search intent much better. - Per-page
<title>, meta description and canonical URL usingPageTitleandHeadContent:
@page "/tools/jwt-decoder"
<PageTitle>JWT Decoder – Decode Tokens Locally | SamToolkit</PageTitle>
<HeadContent>
<meta name="description" content="Decode JWT headers and payloads in your browser. Your token never leaves your device." />
<link rel="canonical" href="https://samtoolkit.com/tools/jwt-decoder" />
</HeadContent>
- Structured data (JSON-LD) describing each tool as a
WebApplication, so search engines understand what the page is. - Real content under each tool. A short explanation, common mistakes, an FAQ. A bare text box with a button doesn't rank; a page that actually explains JWTs does.
Lesson 3: Be honest about what "privacy-first" means
"Nothing leaves your browser" is a strong claim, so it has to be true. A few rules I follow:
- No input is ever sent to an API for the client-side tools. Not for "analytics", not for "improving the service".
- No saving input to remote storage. If a tool remembers something between visits, it stays in your browser.
- Some tools genuinely can't be fully client-side. A webhook inspector needs somewhere to receive requests; an SSL checker needs to connect to a remote server, which browsers don't let JavaScript do directly. For those, I say clearly on the page what goes over the network and why, rather than hiding it under the same "100% local" banner.
Privacy is a trust thing. One misleading label undoes all the rest.
Lesson 4: JS interop only where you must
Blazor covers most things, but some browser APIs are simpler through a tiny JS function — copying to the clipboard, downloading a generated file:
await JS.InvokeVoidAsync("navigator.clipboard.writeText", output);
I keep interop in a single service so the tool components stay pure C#.
What's in the toolkit now
About 30 tools across 7 categories, including:
- Debugging & inspection: JWT Decoder, Diff Checker (text and JSON), Timestamp Converter, HTTP Status & Header Reference
- Security & config: Env Validator,
.gitignoreGenerator, SSL Certificate Checker - Content generators: README Generator
- Text & data: Case Converter, formatters and encoders
Would I choose Blazor WASM again?
For this project, yes. The first-load cost is real and the SEO needs extra effort, but in return I got:
- one language across the whole codebase,
- a privacy guarantee enforced by the architecture,
- near-zero hosting cost,
- tools that keep working offline.
If you're a .NET developer who has been wondering whether Blazor WebAssembly is practical for a real public product: it is, as long as you respect the payload size and treat SEO as a feature, not an afterthought.
You can try the tools at samtoolkit.com. Open DevTools, watch the Network tab, and check the "nothing leaves your browser" claim for yourself — I'd genuinely love feedback, and ideas for the next tool.
What dev tool do you still paste sensitive data into? Tell me in the comments — it might be the next one I build.