The Problem We Wanted to Solve
Every day, developers, designers, and content creators juggle dozens of small tasks — merge a PDF, convert an audio file, compress an image, generate a QR code, polish some text with AI. The typical workflow? Open a browser, search for an online tool, upload your file, wait, download the result. Repeat for the next task. Repeat for the next tool.
The problems with this approach are obvious:
- Privacy: You're uploading sensitive documents to random websites
- Speed: Browser-based tools are slower than native processing
- Fragmentation: Each tool lives on a different site with different UI
- Offline: No internet, no tools
We wanted to build one desktop application that bundles all these utilities together — running locally, respecting privacy, and feeling like a native app.
This is the story of how ToolKnit went from a web prototype to a polished Windows .exe with a custom installer and dual-source CDN deployment.
Phase 1: The Web Prototype
Before committing to a desktop framework, we built a web-based prototype to validate the concept. The idea was simple: a single-page app with a sidebar navigation, dark theme, and tool cards for each category.
The prototype covered 9 tool categories:
- Home — Dashboard with usage stats and navigation
- PDF Tools — Merge, split, compress, encrypt/decrypt, rotate, watermark
- Image Tools — Format conversion, batch compress, crop, collage, watermark removal
- Audio Tools — Format conversion, clipping, BPM detection, channel processing
- Video Tools — Format conversion, compression, screenshot extraction
- Text Tools — Full/half-width conversion, case conversion, JSON formatting
- Calculator Tools — Scientific computation, unit conversion, base conversion
- Creative Tools — QR code generation, color picker, password generator
- AI Tools — Translation, polishing, document generation, role play
The web prototype proved the concept, but it had limitations:
- File processing required server-side APIs (or WASM, which was complex for video/audio)
- Users had to be online
- No drag-and-drop file system access
- Browser security sandbox limited what we could do with local files
We needed to go native.
Phase 2: DR41 — The Tauri Migration
We evaluated several options for the desktop migration:
| Framework | Bundle Size | Native Feel | Rust Ecosystem | Complexity |
|---|---|---|---|---|
| Electron | ~150MB | Good | No | Low |
| Tauri | ~12MB | Excellent | Yes | Medium |
| Flutter Desktop | ~20MB | Good | No | Medium |
We chose Tauri 2.x for three reasons:
-
Tiny bundle size — The final
.exeis ~46MB (including FFmpeg), compared to Electron's typical 150MB+ - Rust backend — Native performance for file processing, PDF manipulation, and media conversion
- Web frontend — We could reuse our existing HTML/CSS/JS frontend almost entirely
Architecture
┌─────────────────────────────────────────┐
│ ToolKnit Desktop │
├─────────────────────────────────────────┤
│ Frontend (HTML/CSS/JS) │
│ ├── Dark theme + WebGL background │
│ ├── 9 tool category pages │
│ ├── i18n (zh/en) with nested keys │
│ └── Spider mascot loading animations │
├─────────────────────────────────────────┤
│ Tauri Bridge (IPC) │
│ ├── invoke() commands │
│ ├── Event listeners │
│ └── Drag & drop file handling │
├─────────────────────────────────────────┤
│ Rust Backend │
│ ├── PDF: pdf-lib-plus-encrypt │
│ ├── Audio/Video: FFmpeg (bundled) │
│ ├── Image: image crate │
│ ├── AI: DeepSeek/OpenAI/Qwen APIs │
│ └── File system access │
├─────────────────────────────────────────┤
│ Native Window │
│ ├── Custom titlebar (no decorations) │
│ ├── System tray with i18n menu │
│ └── 1100x800 fixed size │
└─────────────────────────────────────────┘
Key Technical Decisions
Custom window decorations: We disabled native window decorations (decorations: false) and built a custom titlebar with minimize/maximize/close buttons, plus a "always on top" toggle. This gave us full control over the visual design.
WebGL dynamic background: The home page features a custom WebGL shader that renders a flowing dark fluid animation. We optimized it by disabling anti-aliasing and alpha channels for performance:
const gl = canvas.getContext('webgl', {
antialias: false,
alpha: false,
premultipliedAlpha: false
});
Bundled FFmpeg: Audio and video tools require FFmpeg. Instead of asking users to install it separately, we bundle a minimal FFmpeg binary and download it on first launch if needed. The download source is selected based on the user's language (more on this below).
i18n with nested keys: We use a nested JSON structure for translations, supporting both Chinese and English:
{"home":{"pdfDecrypt":{"title":"PDF Decrypt","dropHere":"Drop your PDF here..."}}}
The language is determined at startup by reading an install_lang.txt file that the installer writes during setup. This file contains the NSIS language ID (1033 for English, 2052 for Simplified Chinese).
Phase 3: The Installer
Distributing a Tauri app is straightforward — npm run tauri build produces an .exe. But we wanted more control over the installation experience.
Custom NSIS Installer
We built a separate Tauri-based installer (toolknit-installer) that:
- Presents a visual wizard with language selection (Chinese/English)
- Downloads the main program zip from CDN
- Extracts it to the user's chosen path
- Creates desktop shortcuts and start menu entries
- Writes
install_config.jsonandinstall_lang.txtfor the main app to read
The installer itself is a Tauri app too — same tech stack, different purpose. It's only ~11.6MB.
Dual-Source CDN Deployment
Here's where it gets interesting. We serve users in both China and internationally. Network latency between China and overseas CDNs can be significant.
We deployed two object storage sources:
| Source | Provider | Region | Use Case |
|---|---|---|---|
| R2 | Cloudflare R2 | Global (CDN) | International/English users |
| RainS3 | RainS3 | China (cn-nb1) | Chinese users |
The download URL is selected based on the user's language preference:
let base_url = if language == "zh" {
"https://toolknit.cn-nb1.rains3.com/toolknit-desktop.zip"
} else {
"https://cdn.24picture.com/toolknit-desktop.zip"
};
Both sources are kept in sync using a Node.js upload script that pushes to both simultaneously:
// Upload zip (main program)
await uploadToClient(r2Client, r2Bucket, 'toolknit-desktop.zip', zipPath, 'application/zip', 'R2');
await uploadToClient(rainClient, rainBucket, 'toolknit-desktop.zip', zipPath, 'application/zip', 'RainS3');
// Upload installer exe
await uploadToClient(r2Client, r2Bucket, 'toolknit-installer.exe', installerPath, 'application/octet-stream', 'R2');
await uploadToClient(rainClient, rainBucket, 'toolknit-installer.exe', installerPath, 'application/octet-stream', 'RainS3');
The installer also has a fallback mechanism — if the primary source fails, it automatically tries the other source:
let urls = [
format!("{}?_t={}", base_url, ts),
format!("{}?_t={}", fallback_url, ts),
];
We append a timestamp parameter to bust CDN cache, ensuring users always get the latest version.
Phase 4: The Download Page
The final piece was a beautiful download page at toolknit.com. We wanted it to showcase the app's features with smooth parallax scrolling and full bilingual support.
Parallax Scrolling with Layered Effects
Each of the 9 tool categories gets its own scrolling module with alternating image/text layouts. We implemented 4 layers of parallax:
- Hero section — Title, subtitle, and description each move at different speeds
- Module images — Images translate at a different rate than the scroll
-
Module content — Text content moves at its own pace, with sub-elements (tag, title, description, features, stats) each having individual
data-speedvalues - Image scaling — Images subtly scale based on scroll position for depth
// Each element has a data-speed attribute
const offset = (elementCenter - viewportCenter) * speed;
el.style.transform = `translateY(${offset}px)`;
Language-Aware Screenshots
The download page supports both English and Chinese. When the user switches languages, not only does the text change, but the screenshots change too — English screenshots show the English UI, Chinese screenshots show the Chinese UI:
const prefix = lang === 'en' ? '1-' : '';
document.querySelectorAll('.module-image img').forEach((img, i) => {
img.src = `https://24picture.com/${prefix}${i + 1}.webp`;
});
The language preference is saved to localStorage, so returning users see their preferred language automatically.
Download Logic
The download buttons on the page follow the same dual-source logic as the installer:
const DOWNLOAD_URLS = {
en: 'https://cdn.24picture.com/toolknit-installer.exe',
zh: 'https://toolknit.cn-nb1.rains3.com/toolknit-installer.exe'
};
function handleDownload(e) {
e.preventDefault();
const lang = localStorage.getItem('toolknit-lang') || 'en';
window.open(DOWNLOAD_URLS[lang], '_blank');
}
Lessons Learned
1. Tauri's CSP is strict but worth it
Tauri enforces a strict Content Security Policy. Every external domain your app connects to must be whitelisted in tauri.conf.json. This forced us to be explicit about our API endpoints and CDN URLs — which is actually a good security practice.
2. File locks on Windows are painful
During development, the Rust compiler would sometimes fail with os error 32 (file in use) because the previous build's .exe was still running. Our solution: a kill script that runs before every build:
taskkill/F/IMtoolknit-desktop.exe2>$nulltaskkill/F/IMcargo.exe2>$null
For first-time full builds, we also set CARGO_BUILD_JOBS=1 to avoid concurrent file access.
3. Dual-source deployment is simpler than it sounds
Using S3-compatible APIs for both Cloudflare R2 and RainS3 means we can use the same AWS SDK code for both. The only difference is the endpoint and credentials. One script, two uploads, done.
4. The installer is part of the product
A great app with a terrible install experience still feels bad. Building a custom Tauri-based installer let us control the entire user journey — from download to first launch — with proper language detection and a clean wizard UI.
Tech Stack Summary
| Layer | Technology |
|---|---|
| Desktop Framework | Tauri 2.x |
| Frontend | Vanilla HTML/CSS/JS |
| Backend | Rust |
| PDF Processing | pdf-lib-plus-encrypt |
| Audio/Video | FFmpeg (bundled) |
| Image Processing | image crate |
| AI Integration | DeepSeek, OpenAI, Qwen APIs |
| Icons | Lucide Icons |
| Object Storage | Cloudflare R2 + RainS3 |
| Upload Script | Node.js + AWS SDK v3 |
| Smooth Scroll | Lenis |
| Background | Custom WebGL shader |
What's Next
ToolKnit is currently in v1.0 Beta for Windows. We're planning:
- macOS and Linux support
- Plugin system for community-contributed tools
- More AI-powered features
- Auto-update mechanism
Try It Out
ToolKnit is free and open for everyone. Download the installer for your platform:
- International users: Download from CDN
- China users: Download from RainS3
Visit toolknit.com to see the full feature showcase, or check out our blog for more development stories.
If you found this article helpful, consider giving it a reaction or following for more posts about desktop development with Tauri, Rust, and modern web technologies.