C.W.K.
Stream
Lesson 02 of 06 · published

Anatomy of a Tauri Project

~13 min · tauri, structure, project, files

Level 0Web Tourist
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"You can't navigate a house you've never walked through. Walk the tree once, deliberately."

The Two Halves, as Folders

Open the project create-tauri-app made and you'll see the two-process model laid out as directories. At the root lives your frontend — exactly what you'd expect from a web app: index.html, src/ with your components, package.json, a bundler config like vite.config.ts. This half is built by your web toolchain, not by Tauri.

Inside src-tauri/ lives the native half, and it's a complete Rust crate. The files you'll touch most:

  • Cargo.toml — Rust dependencies (this is where tauri = "2" and your plugins are declared).
  • src/main.rs — the tiny binary entry (calls run()).
  • src/lib.rs — where run() actually lives: the Builder, plugins, commands, setup.
  • tauri.conf.json — the config that wires frontend to core (next lesson).
  • capabilities/ — the security grants (the security track).
  • icons/ — app icons for every platform.
  • build.rs + gen/ — generated build glue and schemas; you rarely hand-edit these.

Why main.rs and lib.rs Are Split

It looks redundant: main.rs is two lines that just call run() in lib.rs. The reason is mobile. On iOS and Android there is no main() you control — the OS calls into your library. By putting all real logic in lib.rs's run() (marked with a mobile entry-point attribute), the same code path serves desktop and mobile. This split is forward-compatibility baked into the scaffold, even if you only target desktop today.

Code

The tree, walked once·text
my-app/
├─ index.html              # frontend entry (the web half)
├─ package.json
├─ vite.config.ts
├─ src/                    # your React/Svelte/etc. components
│   ├─ main.tsx
│   └─ App.tsx
└─ src-tauri/              # the native half — a Rust crate
    ├─ Cargo.toml          # tauri = "2", plugins, your deps
    ├─ tauri.conf.json     # wires frontend ⇄ core
    ├─ build.rs            # generated build glue
    ├─ capabilities/       # security grants (default.json)
    ├─ icons/              # per-platform app icons
    ├─ gen/                # generated schemas (don't hand-edit)
    └─ src/
        ├─ main.rs         # 2 lines → calls run()
        └─ lib.rs          # run(): Builder, plugins, commands

External links

Exercise

In the app you scaffolded, open src-tauri/src/main.rs and src-tauri/src/lib.rs side by side. Confirm main.rs only calls run(). Then find the line in lib.rs where commands would be registered (the generate_handler! macro) — it may be empty. You're locating the exact spot where the bridge track will plug in.
Hint
generate_handler![] is inside the Builder chain in lib.rs (or run()). An empty list is normal for a fresh scaffold — that's the socket your first command plugs into.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.