Tuono: Next.js, but in Rust 🦀

Do you want faster React website ? But you have tried every meta-framework: Angular, Svelte, Solid or Qwik ? Try Tuono !

Fri, Apr, 18, 2025
Adam Hitzger's blog - Do you want faster React website ? But you have tried every meta-framework: Angular, Svelte, Solid o

Yep, another React framework. But with a difference that backend is written in Rust, Axum. together they create Tuono!

To install Tuono, use the following command:
In today’s tutorial, we’ll go over how to install Rust and Tuono, how to get oriented within the project, and we’ll try setting up an API endpoint that logs data received from a form. You’ll also need to have the following installed Node.js

To install Rust, run this command!

example.sh
1curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Pro instalaci Tuona použijte tento příkaz:

example.sh
1cargo install tuono 2rustup install nightly 3

If everything is installed, let's move on to creating the project.

example.sh
1tuono new example --template with-tailwind 2cd example/ 3npm install 4rustup override set nightly 5cargo add serde 6npm i react-hot-toast

The commands above will create the project and install the necessary npm packages. The --template with-tailwind flag downloads the version of Tuono with Tailwind, using the latest version 4. Next, set the project to use the nightly Rust toolchain and install serde for serialization. For toast notifications, install react-hot-toast. Once everything is installed, start the server!

example.sh
1tuono dev

Also, open the project in your IDE so we can play around with it a bit.

Open the tuono.config.ts file and adjust the alias for importing components.

example.typescript
1const config: TuonoConfig = { 2 vite: { 3 alias: { 4 '@': 'src', 5 }, 6 plugins: [tailwindcss()], 7 }, 8}

Folder and file structure

[object Object]

In the src folder, you'll find routing, styles, and API endpoints. Just like in Next.js, new routes can be created by making a folder and placing a file named index.tsx inside it. Sometimes, a __layout.tsx file is also needed. Additionally, Tuono introduces a new .rs file — index.rs. This file functions similarly to getServerSideProps in Next.js. It's used to load data from the database and pass it to the frontend.

example.undefined
1use serde::Serialize; 2use tuono_lib::{Props, Request, Response}; 3 4#[derive(Serialize)] 5struct MyResponse<'a> { 6 subtitle: &'a str, 7} 8 9#[tuono_lib::handler] 10async fn get_server_side_props(_req: Request) -> Response { 11 Response::Props(Props::new(MyResponse { 12 subtitle: "The react / rust fullstack framework", 13 })) 14}

We'll look at more complex data fetching from servers next time. For now, let's create a form that will process and handle our data.

example.html
1<form 2 action="/api/formdata" 3 method='POST' 4 className='m-10 flex flex-col space-y-2'> 5 <h1>Sign Up Form</h1> 6 <label htmlFor="name">Name:</label> 7 <input 8 name='name' 9 id='name' 10 type="text" 11 placeholder='Enter your name' 12 className='border px-2 w-96 border-black rounded-xl'/> 13 <label htmlFor="age">Age:</label> 14 <input 15 name='age' 16 id='age' 17 type="number" 18 placeholder='Enter your age' 19 className='border px-2 w-96 border-black rounded-xl'/> 20 <label htmlFor="email">Email:</label> 21 <input 22 name='email' 23 id='email' type="email" 24 placeholder='Enter your email' 25 className='border px-2 w-96 border-black rounded-xl'/> 26 <label htmlFor="password">Password:</label> 27 <input 28 name='password' 29 id='password' 30 type="text" 31 placeholder='Enter your name' 32 className='border px-2 w-96 border-black rounded-xl'/> 33 <button type='submit'>Submit</button> 34 </form>

Next, create an api folder inside the src/routes directory, and within the api folder, create a formdata.rs file, which will serve as our endpoint.

In the formdata.rs file, we'll process the data, print it out, and redirect the user back.

example.text
1use tuono_lib::{axum::response::Redirect, Request}; 2use serde::Deserialize; 3 4#[derive(Deserialize)] 5struct FormData { 6 name: String, 7 age: i8, 8 email: String, 9 password: String, 10} 11 12 13#[tuono_lib::api(POST)] 14async fn send_data(_req: Request) -> Redirect { 15 let data = _req.form_data::<FormData>().unwrap(); 16 let mut message= "/?message=Hello ".to_string(); 17 println!("Name: {}, Age: {}, Email: {}, Password. {}", data.name, data.age, data.email, data.password); 18 message.push_str(&data.name); 19 Redirect::to(&message.to_string()) 20}

First, we'll create the structure of our data, followed by a function to process it. The macro above it indicates that this is not a handler, but a POST-type API route. All functions must accept a Request, which contains methods like params, body, or form_data(). I also created a message variable to return status via search parameters.

In index.tsx, we'll also add a function to handle search parameters.

example.tsx
1const path = useRouter() 2 useEffect(()=> { 3 const msg = path.query?.message; 4 console.log(msg) 5 if( msg && typeof msg === "string"){ 6 toast.success(decodeURIComponent(msg)) 7 } 8 }, [path.query])

In __layout.stx, let's also place the Toaster component.

example.tsx
1import type { JSX } from 'react' 2import { TuonoScripts } from 'tuono' 3import type { TuonoLayoutProps } from 'tuono' 4import { Toaster } from 'react-hot-toast' 5import '../styles/global.css' 6 7export default function RootLayout({ 8 children, 9}: TuonoLayoutProps): JSX.Element { 10 return ( 11 <html> 12 <head> 13 <meta name="viewport" content="width=device-width, initial-scale=1" /> 14 </head> 15 <body> 16 <main>{children}</main> 17 <TuonoScripts /> 18 <Toaster/> 19 </body> 20 </html> 21 ) 22} 23

And we are done! Check Tuono official websites.

[object Object]

Subscribe to my newsletter !

and be updated with lastest libraries. Enter your email address!