diff --git a/crates/tuono/Cargo.toml b/crates/tuono/Cargo.toml
index aeb29f66..8961002b 100644
--- a/crates/tuono/Cargo.toml
+++ b/crates/tuono/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "tuono"
-version = "0.2.5"
+version = "0.3.0"
 edition = "2021"
 authors = ["V. Ageno <valerioageno@yahoo.it>"]
 description = "The react/rust fullstack framework"
diff --git a/crates/tuono/src/source_builder.rs b/crates/tuono/src/source_builder.rs
index 0c5152fc..a52fb97b 100644
--- a/crates/tuono/src/source_builder.rs
+++ b/crates/tuono/src/source_builder.rs
@@ -153,7 +153,7 @@ impl Route {
         }
 
         Route {
-            module_import: module.as_str().to_string().replace('/', "_"),
+            module_import: module.as_str().to_string().replace('/', "_").to_lowercase(),
             axum_route,
         }
     }
@@ -329,6 +329,7 @@ mod tests {
             "/home/user/Documents/tuono/src/routes/index.rs",
             "/home/user/Documents/tuono/src/routes/posts/index.rs",
             "/home/user/Documents/tuono/src/routes/posts/[post].rs",
+            "/home/user/Documents/tuono/src/routes/posts/UPPERCASE.rs",
         ];
 
         routes
@@ -340,6 +341,7 @@ mod tests {
             ("/about.rs", "about"),
             ("/posts/index.rs", "posts_index"),
             ("/posts/[post].rs", "posts_dyn_post"),
+            ("/posts/UPPERCASE.rs", "posts_uppercase"),
         ];
 
         results.into_iter().for_each(|(path, module_import)| {
diff --git a/crates/tuono_lib/Cargo.toml b/crates/tuono_lib/Cargo.toml
index 03271b4e..67a59fb3 100644
--- a/crates/tuono_lib/Cargo.toml
+++ b/crates/tuono_lib/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "tuono_lib"
-version = "0.2.5"
+version = "0.3.0"
 edition = "2021"
 authors = ["V. Ageno <valerioageno@yahoo.it>"]
 description = "The react/rust fullstack framework"
@@ -24,8 +24,9 @@ serde = { version = "1.0.202", features = ["derive"] }
 erased-serde = "0.4.5"
 serde_json = "1.0"
 
-tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.2.5"}
+tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.3.0"}
 once_cell = "1.19.0"
 lazy_static = "1.5.0"
 regex = "1.10.5"
+either = "1.13.0"
 
diff --git a/crates/tuono_lib/src/response.rs b/crates/tuono_lib/src/response.rs
index f217b55f..8b599b58 100644
--- a/crates/tuono_lib/src/response.rs
+++ b/crates/tuono_lib/src/response.rs
@@ -1,7 +1,7 @@
 use crate::Request;
 use crate::{ssr::Js, Payload};
 use axum::http::StatusCode;
-use axum::response::{Html, IntoResponse};
+use axum::response::{Html, IntoResponse, Redirect, Response as AxumResponse};
 use axum::Json;
 use erased_serde::Serialize;
 
@@ -15,6 +15,41 @@ pub enum Response {
     Props(Props),
 }
 
+#[derive(serde::Serialize)]
+struct JsonResponseInfo {
+    redirect_destination: Option<String>,
+}
+
+impl JsonResponseInfo {
+    fn new(redirect_destination: Option<String>) -> JsonResponseInfo {
+        JsonResponseInfo {
+            redirect_destination,
+        }
+    }
+}
+
+#[derive(serde::Serialize)]
+struct JsonResponse<'a> {
+    data: Option<&'a dyn Serialize>,
+    info: JsonResponseInfo,
+}
+
+impl<'a> JsonResponse<'a> {
+    fn new(props: &'a dyn Serialize) -> Self {
+        JsonResponse {
+            data: Some(props),
+            info: JsonResponseInfo::new(None),
+        }
+    }
+
+    fn new_redirect(destination: String) -> Self {
+        JsonResponse {
+            data: None,
+            info: JsonResponseInfo::new(Some(destination)),
+        }
+    }
+}
+
 impl Props {
     pub fn new(data: impl Serialize + 'static) -> Self {
         Props {
@@ -32,25 +67,32 @@ impl Props {
 }
 
 impl Response {
-    pub fn render_to_string(&self, req: Request) -> impl IntoResponse {
+    pub fn render_to_string(&self, req: Request) -> AxumResponse {
         match self {
             Self::Props(Props { data, http_code }) => {
                 let payload = Payload::new(&req, data).client_payload().unwrap();
 
                 match Js::SSR.with(|ssr| ssr.borrow_mut().render_to_string(Some(&payload))) {
-                    Ok(html) => (*http_code, Html(html)),
-                    Err(_) => (*http_code, Html("500 Internal server error".to_string())),
+                    Ok(html) => (*http_code, Html(html)).into_response(),
+                    Err(_) => {
+                        (*http_code, Html("500 Internal server error".to_string())).into_response()
+                    }
                 }
             }
-            // TODO: Handle here other enum arms
-            _ => todo!(),
+            Self::Redirect(to) => Redirect::permanent(to).into_response(),
         }
     }
 
     pub fn json(&self) -> impl IntoResponse {
         match self {
-            Self::Props(Props { data, http_code }) => (*http_code, Json(data)).into_response(),
-            _ => (StatusCode::INTERNAL_SERVER_ERROR, axum::Json("{}")).into_response(),
+            Self::Props(Props { data, http_code }) => {
+                (*http_code, Json(JsonResponse::new(data))).into_response()
+            }
+            Self::Redirect(destination) => (
+                StatusCode::PERMANENT_REDIRECT,
+                Json(JsonResponse::new_redirect(destination.to_string())),
+            )
+                .into_response(),
         }
     }
 }
diff --git a/crates/tuono_lib_macros/Cargo.toml b/crates/tuono_lib_macros/Cargo.toml
index e5118b50..81675db3 100644
--- a/crates/tuono_lib_macros/Cargo.toml
+++ b/crates/tuono_lib_macros/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "tuono_lib_macros"
-version = "0.2.5"
+version = "0.3.0"
 edition = "2021"
 description = "The react/rust fullstack framework"
 repository = "https://github.com/Valerioageno/tuono"
diff --git a/docs/tutorial.md b/docs/tutorial.md
index 1cb28d46..77ab7ea7 100644
--- a/docs/tutorial.md
+++ b/docs/tutorial.md
@@ -23,6 +23,7 @@ Typescript and Rust knowledge is not a requirement though!
 * [Create a stand-alone component](#create-a-stand-alone-component)
 * [Create the /pokemons/[pokemon] route](#create-the-pokemonspokemon-route)
 * [Error handling](#error-handling)
+* [Handle redirections](#handle-redirections)
 * [Building for production](#building-for-production)
 * [Conclusion](#conclusion)
 
@@ -98,7 +99,7 @@ The file `index.rs` represents the server side capabilities for the index route.
 
 - Passing server side props
 - Changing http status code
-- Redirect/Rewrite to a different route (Available soon)
+- Redirecting to a different route
 
 ## Tutorial introduction
 
@@ -542,6 +543,41 @@ async fn get_all_pokemons(_req: Request<'_>, fetch: reqwest::Client) -> Response
 If you now try to load a not existing pokemon (`http://localhost:3000/pokemons/tuono-pokemon`) you will 
 correctly receive a 404 status code in the console.
 
+## Handle redirections
+
+What if there is a pokemon among all of them that should be considered the GOAT? What
+we are going to do right now is creating a new route `/pokemons/GOAT` that points to the best
+pokemon of the first generation.
+
+First let's create a new route by just creating an new file `/pokemons/GOAT.rs` and pasting the following code:
+
+```rs
+// src/routes/pokemons/GOAT.rs
+use tuono_lib::{Request, Response};
+
+#[tuono_lib::handler]
+async fn redirect_to_goat(_: Request<'_>, _: reqwest::Client) -> Response {
+    // Of course the GOAT is mewtwo - feel free to select your favourite 😉
+    Response::Redirect("/pokemons/mewtwo".to_string()) 
+}
+```
+
+Now let's create the button in the home page to actually point to it!
+
+```diff
+// src/routes/index.tsx
+
+<ul style={{ flexWrap: 'wrap', display: 'flex', gap: 10 }}>
+++      <PokemonLink pokemon={{ name: 'GOAT' }} id={0} />
+        {data.results.map((pokemon, i) => {
+          return <PokemonLink pokemon={pokemon} id={i + 1} key={i} />
+        })}
+</ul>
+```
+
+Now at [http://localhost:3000/](http:/localhost:3000/) you will find a new link at the beginning of the list.
+Click on it and see the application automatically redirecting you to your favourite pokemon's route!
+
 ## Building for production
 
 The source now is ready to be released. Both server and client have been managed in a unoptimized way
diff --git a/examples/tutorial/src/routes/index.tsx b/examples/tutorial/src/routes/index.tsx
index 25105096..806d0732 100644
--- a/examples/tutorial/src/routes/index.tsx
+++ b/examples/tutorial/src/routes/index.tsx
@@ -37,6 +37,7 @@ export default function IndexPage({
         </div>
       </div>
       <ul style={{ flexWrap: 'wrap', display: 'flex', gap: 10 }}>
+        <PokemonLink pokemon={{ name: 'GOAT' }} id={0} />
         {data.results.map((pokemon, i) => {
           return <PokemonLink pokemon={pokemon} id={i + 1} key={i} />
         })}
diff --git a/examples/tutorial/src/routes/pokemons/GOAT.rs b/examples/tutorial/src/routes/pokemons/GOAT.rs
new file mode 100644
index 00000000..f8bcd5df
--- /dev/null
+++ b/examples/tutorial/src/routes/pokemons/GOAT.rs
@@ -0,0 +1,7 @@
+// src/routes/pokemons/GOAT.rs
+use tuono_lib::{Request, Response};
+
+#[tuono_lib::handler]
+async fn redirect_to_goat(_: Request<'_>, _: reqwest::Client) -> Response {
+    Response::Redirect("/pokemons/mewtwo".to_string())
+}
diff --git a/packages/lazy-fn-vite-plugin/package.json b/packages/lazy-fn-vite-plugin/package.json
index 7a600871..6da5d874 100644
--- a/packages/lazy-fn-vite-plugin/package.json
+++ b/packages/lazy-fn-vite-plugin/package.json
@@ -1,6 +1,6 @@
 {
   "name": "tuono-lazy-fn-vite-plugin",
-  "version": "0.2.5",
+  "version": "0.3.0",
   "description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework",
   "scripts": {
     "dev": "vite build --watch",
diff --git a/packages/tuono/package.json b/packages/tuono/package.json
index 727cc50b..fe73c7a3 100644
--- a/packages/tuono/package.json
+++ b/packages/tuono/package.json
@@ -1,6 +1,6 @@
 {
   "name": "tuono",
-  "version": "0.2.5",
+  "version": "0.3.0",
   "description": "The react/rust fullstack framework",
   "scripts": {
     "dev": "vite build --watch",
diff --git a/packages/tuono/src/router/components/RouterProvider.tsx b/packages/tuono/src/router/components/RouterProvider.tsx
index 10ca25e0..596f80de 100644
--- a/packages/tuono/src/router/components/RouterProvider.tsx
+++ b/packages/tuono/src/router/components/RouterProvider.tsx
@@ -1,7 +1,9 @@
 import { getRouterContext } from './RouterContext'
 import { Matches } from './Matches'
-import { useRouterStore } from '../hooks/useRouterStore'
-import React, { useEffect, useLayoutEffect, type ReactNode } from 'react'
+import { useListenBrowserUrlUpdates } from '../hooks/useListenBrowserUrlUpdates'
+import React, { type ReactNode } from 'react'
+import { initRouterStore } from '../hooks/useRouterStore'
+import type { ServerProps } from '../types'
 
 type Router = any
 
@@ -15,11 +17,6 @@ interface RouterProviderProps {
   serverProps?: ServerProps
 }
 
-interface ServerProps {
-  router: Location
-  props: any
-}
-
 function RouterContextProvider({
   router,
   children,
@@ -47,58 +44,13 @@ function RouterContextProvider({
   )
 }
 
-const initRouterStore = (props?: ServerProps): void => {
-  const updateLocation = useRouterStore((st) => st.updateLocation)
-
-  if (typeof window === 'undefined') {
-    updateLocation({
-      pathname: props?.router.pathname || '',
-      hash: '',
-      href: '',
-      searchStr: '',
-    })
-  }
-
-  useLayoutEffect(() => {
-    const { pathname, hash, href, search } = window.location
-    updateLocation({
-      pathname,
-      hash,
-      href,
-      searchStr: search,
-      search: new URLSearchParams(search),
-    })
-  }, [])
-}
-
-const useListenUrlUpdates = (): void => {
-  const updateLocation = useRouterStore((st) => st.updateLocation)
-
-  const updateLocationOnPopStateChange = ({ target }: any): void => {
-    const { pathname, hash, href, search } = target.location
-    updateLocation({
-      pathname,
-      hash,
-      href,
-      searchStr: search,
-      search: new URLSearchParams(search),
-    })
-  }
-  useEffect(() => {
-    window.addEventListener('popstate', updateLocationOnPopStateChange)
-    return (): void => {
-      window.removeEventListener('popstate', updateLocationOnPopStateChange)
-    }
-  }, [])
-}
-
 export function RouterProvider({
   router,
   serverProps,
 }: RouterProviderProps): JSX.Element {
   initRouterStore(serverProps)
 
-  useListenUrlUpdates()
+  useListenBrowserUrlUpdates()
 
   return (
     <RouterContextProvider router={router}>
diff --git a/packages/tuono/src/router/hooks/useListenBrowserUrlUpdates.tsx b/packages/tuono/src/router/hooks/useListenBrowserUrlUpdates.tsx
new file mode 100644
index 00000000..7f5fafab
--- /dev/null
+++ b/packages/tuono/src/router/hooks/useListenBrowserUrlUpdates.tsx
@@ -0,0 +1,27 @@
+import { useRouterStore } from './useRouterStore'
+import { useEffect } from 'react'
+
+/*
+ * This hook is meant to handle just browser related location updates
+ * like the back and forward buttons.
+ */
+export const useListenBrowserUrlUpdates = (): void => {
+  const updateLocation = useRouterStore((st) => st.updateLocation)
+
+  const updateLocationOnPopStateChange = ({ target }: any): void => {
+    const { pathname, hash, href, search } = target.location
+    updateLocation({
+      pathname,
+      hash,
+      href,
+      searchStr: search,
+      search: new URLSearchParams(search),
+    })
+  }
+  useEffect(() => {
+    window.addEventListener('popstate', updateLocationOnPopStateChange)
+    return (): void => {
+      window.removeEventListener('popstate', updateLocationOnPopStateChange)
+    }
+  }, [])
+}
diff --git a/packages/tuono/src/router/hooks/useRouterStore.tsx b/packages/tuono/src/router/hooks/useRouterStore.tsx
index c9fd4019..faf4a5cc 100644
--- a/packages/tuono/src/router/hooks/useRouterStore.tsx
+++ b/packages/tuono/src/router/hooks/useRouterStore.tsx
@@ -1,4 +1,7 @@
 import { create } from 'zustand'
+import { useLayoutEffect } from 'react'
+
+import type { ServerProps } from '../types'
 
 export interface ParsedLocation {
   href: string
@@ -20,6 +23,30 @@ interface RouterState {
   updateLocation: (loc: ParsedLocation) => void
 }
 
+export const initRouterStore = (props?: ServerProps): void => {
+  const updateLocation = useRouterStore((st) => st.updateLocation)
+
+  if (typeof window === 'undefined') {
+    updateLocation({
+      pathname: props?.router.pathname || '',
+      hash: '',
+      href: '',
+      searchStr: '',
+    })
+  }
+
+  useLayoutEffect(() => {
+    const { pathname, hash, href, search } = window.location
+    updateLocation({
+      pathname,
+      hash,
+      href,
+      searchStr: search,
+      search: new URLSearchParams(search),
+    })
+  }, [])
+}
+
 export const useRouterStore = create<RouterState>()((set) => ({
   isLoading: false,
   isTransitioning: false,
diff --git a/packages/tuono/src/router/hooks/useServerSideProps.tsx b/packages/tuono/src/router/hooks/useServerSideProps.tsx
index 9ec8628a..69c8ac29 100644
--- a/packages/tuono/src/router/hooks/useServerSideProps.tsx
+++ b/packages/tuono/src/router/hooks/useServerSideProps.tsx
@@ -1,6 +1,7 @@
 import { useState, useEffect, useRef } from 'react'
 import type { Route } from '../route'
 import { useRouterStore } from './useRouterStore'
+import { fromUrlToParsedLocation } from '../utils/from-url-to-parsed-location'
 
 const isServer = typeof document === 'undefined'
 
@@ -15,6 +16,19 @@ declare global {
   }
 }
 
+interface TuonoApi {
+  data?: any
+  info: {
+    redirect_destination?: string
+  }
+}
+
+const fetchClientSideData = async (): Promise<TuonoApi> => {
+  const res = await fetch(`/__tuono/data${location.pathname}`)
+  const data: TuonoApi = await res.json()
+  return data
+}
+
 /*
  * Use the props provided by the SSR and dehydrate the
  * props for client side usage.
@@ -27,7 +41,10 @@ export function useServerSideProps<T>(
   serverSideProps: T,
 ): UseServerSidePropsReturn {
   const isFirstRendering = useRef<boolean>(true)
-  const location = useRouterStore((st) => st.location)
+  const [location, updateLocation] = useRouterStore((st) => [
+    st.location,
+    st.updateLocation,
+  ])
   const [isLoading, setIsLoading] = useState<boolean>(
     // Force loading if has handler
     route.options.hasHandler &&
@@ -53,8 +70,22 @@ export function useServerSideProps<T>(
       ;(async (): Promise<void> => {
         setIsLoading(true)
         try {
-          const res = await fetch(`/__tuono/data${location.pathname}`)
-          setData(await res.json())
+          const response = await fetchClientSideData()
+          if (response.info.redirect_destination) {
+            const parsedLocation = fromUrlToParsedLocation(
+              response.info.redirect_destination,
+            )
+
+            history.pushState(
+              parsedLocation.pathname,
+              '',
+              parsedLocation.pathname,
+            )
+
+            updateLocation(parsedLocation)
+            return
+          }
+          setData(response.data)
         } catch (error) {
           throw Error('Failed loading Server Side Data', { cause: error })
         } finally {
diff --git a/packages/tuono/src/router/types.ts b/packages/tuono/src/router/types.ts
index 3ba2ea71..2daff8cc 100644
--- a/packages/tuono/src/router/types.ts
+++ b/packages/tuono/src/router/types.ts
@@ -2,3 +2,8 @@ export interface Segment {
   type: 'pathname' | 'param' | 'wildcard'
   value: string
 }
+
+export interface ServerProps {
+  router: Location
+  props: any
+}
diff --git a/packages/tuono/src/router/utils/from-url-to-parsed-location.ts b/packages/tuono/src/router/utils/from-url-to-parsed-location.ts
new file mode 100644
index 00000000..b269acc3
--- /dev/null
+++ b/packages/tuono/src/router/utils/from-url-to-parsed-location.ts
@@ -0,0 +1,16 @@
+import type { ParsedLocation } from '../hooks/useRouterStore'
+
+// TODO: improve the whole react/rust URL parsing logic
+export function fromUrlToParsedLocation(href: string): ParsedLocation {
+  /*
+   * This function works on both server and client.
+   * For this reason we can't rely on the browser's URL api
+   */
+  return {
+    href,
+    pathname: href,
+    search: undefined,
+    searchStr: '',
+    hash: '',
+  }
+}