Compare commits

...

14 Commits

Author SHA1 Message Date
Valerio Ageno aaae7db24d Improve route preloading (#80)
* feat: improve route preloading

* feat: update version to v0.11.2

---------

Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
2024-11-05 12:03:20 +01:00
Valerio Ageno 48c3934b6e Add seo tags to documentation (#78)
* refactor: update documentation meta tags

* feat: update documentation version to v0.11.1

* fix: formatting

* feat: add SEO breadcrumb structured data
2024-11-04 21:23:56 +01:00
Valerio Ageno 258b4af0b1 feat: prevent scroll offset on page navigation (#77) 2024-11-04 19:29:37 +01:00
Valerio Ageno edfbd03f13 Prevent not exported routes error (#76)
* fix: prevent no exported route compiler error

* feat: update version to v0.11.1

* fix: formatting
2024-11-04 19:25:46 +01:00
Valerio Ageno 9cb0f62da7 fix: documentation server/client mismatch 2024-11-04 17:15:37 +01:00
Valerio Ageno 9591960e31 feat: add canonical tags to documentation 2024-11-03 22:34:23 +01:00
Valerio Ageno 132d8ebe8f feat: add navigation buttons to tutorial 2024-11-03 22:34:12 +01:00
Valerio Ageno 4020073aca feat: update doc tuono version to v0.11.0 2024-11-03 22:34:00 +01:00
Valerio Ageno 5025692220 fix: put mantine color scheme component outside Head 2024-11-03 22:27:17 +01:00
Valerio Ageno d04a4c4103 Move to react-helmet-async (#72)
* feat: move to react-helmet-async

* feat: set version to v0.11.0
2024-11-03 21:09:32 +01:00
renovate[bot] 7e6dea68f4 chore(deps): update pnpm to v9.12.3 (#58)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-03 19:02:59 +01:00
renovate[bot] a046833414 chore(deps): update dependency node to v22 (#71)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-03 19:02:43 +01:00
renovate[bot] cfa59ca8bf fix(deps): update dependency @types/node to v22 (#70) 2024-10-29 07:02:39 +01:00
renovate[bot] d470bd58f2 doc: update tuono version to v0.10.4 (#61)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
2024-10-18 15:22:54 +02:00
58 changed files with 587 additions and 257 deletions
+40 -40
View File
@@ -18,32 +18,32 @@ jobs:
defaults:
run:
working-directory: ./apps/documentation
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version: 20
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Setup rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install tuono
run: cargo install tuono@0.10.3
- name: Setup rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install pnpm
run: npm i -g pnpm
- name: Install dependencies
run: pnpm install --ignore-workspace
- name: Install tuono
run: cargo install tuono@0.10.4
- name: Build project
run: tuono build --static
- name: Install pnpm
run: npm i -g pnpm
- name: Install dependencies
run: pnpm install --ignore-workspace
- name: Build project
run: tuono build --static
fmt-lint-and-types:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
@@ -53,23 +53,23 @@ jobs:
run:
working-directory: ./apps/documentation
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Install pnpm
run: npm i -g pnpm
- name: Install dependencies
run: pnpm install --ignore-workspace
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Check formatting
run: pnpm format:check
- name: Lint
run: pnpm lint
- name: Types
run: pnpm types
- name: Install pnpm
run: npm i -g pnpm
- name: Install dependencies
run: pnpm install --ignore-workspace
- name: Check formatting
run: pnpm format:check
- name: Lint
run: pnpm lint
- name: Types
run: pnpm types
+4 -4
View File
@@ -16,7 +16,7 @@ jobs:
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
steps:
steps:
- name: Check out code
uses: actions/checkout@v4
with:
@@ -28,14 +28,14 @@ jobs:
node-version: 20
- name: Setup rust
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install tuono
run: cargo install tuono@0.10.3
run: cargo install tuono@0.10.4
- name: Install pnpm
run: npm i -g pnpm
- name: Install dependencies
run: pnpm install --ignore-workspace
+1 -1
View File
@@ -1 +1 @@
20.18.0
22.11.0
+1 -1
View File
@@ -8,7 +8,7 @@ name = "tuono"
path = ".tuono/main.rs"
[dependencies]
tuono_lib = "0.10.3"
tuono_lib = "0.11.1"
glob = "0.3.1"
time = { version = "0.3", features = ["macros"] }
serde = { version = "1.0.202", features = ["derive"] }
+1 -1
View File
@@ -17,7 +17,7 @@
"clsx": "^2.1.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"tuono": "0.10.3"
"tuono": "0.11.1"
},
"devDependencies": {
"@types/mdx": "^2.0.13",
Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

@@ -1,31 +1,67 @@
import { Breadcrumbs, Button } from '@mantine/core'
import { Link } from 'tuono'
import { Link, Head } from 'tuono'
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
import type { ReactNode } from 'react'
interface Breadcrumb {
href?: string
label: string
}
interface BreadcrumbsProps {
breadcrumbs: Breadcrumb[]
}
export default function TuonoBreadcrumbs({
children,
}: {
children: ReactNode
}): JSX.Element {
breadcrumbs = [],
}: BreadcrumbsProps): JSX.Element {
return (
<Breadcrumbs
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
mb="md"
mt="md"
>
<Button
href="/documentation"
component={Link}
variant="subtle"
radius="xl"
p={5}
<>
<Head>
<script type="application/ld+json">
{`[
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
'@type': "ListItem",
"position": 1,
"name": "Tuono - Documentation",
"item": "https://tuono.dev/documentation",
}${breadcrumbs.length > 0 ? ',' : ''}
${breadcrumbs
.map((br, i) =>
JSON.stringify({
'@type': 'ListItem',
position: i + 2,
name: br.label,
item: br.href ? `https://tuono.dev${br.href}` : undefined,
}),
)
.join(',')}]
}
]`}
</script>
</Head>
<Breadcrumbs
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
mb="md"
mt="md"
>
<IconBolt />
</Button>
{children}
</Breadcrumbs>
<Button
href="/documentation"
component={Link}
variant="subtle"
radius="xl"
p={5}
>
<IconBolt />
</Button>
{breadcrumbs.map((br) => (
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
))}
</Breadcrumbs>
</>
)
}
@@ -0,0 +1,3 @@
import MetaTags from './meta-tags'
export default MetaTags
@@ -0,0 +1,41 @@
import type { ReactNode } from 'react'
import { Head } from 'tuono'
interface MetaTagsProps {
title: string
description?: string
type?: 'website' | 'article' | 'book' | 'video.movie' | 'music.song'
canonical: string
}
export default function MetaTags({
title,
description,
type = 'article',
canonical,
}: MetaTagsProps): ReactNode {
return (
<Head>
<title>{title}</title>
<link rel="canonical" href={canonical} />
<meta
name="description"
content={
description ??
'The technologies we love seamlessly working together to unleash the highest web performance ever met on React'
}
/>
<meta
name="keywords"
content="ReactJs, Typescript, Rust, JavaScript, Fullstack framework, Web development"
/>
<meta property="og:title" content={title} />
<meta property="og:type" content={type} />
<meta property="og:image" content="https://tuono.dev/og-cover.png" />
<meta property="og:url" content={canonical} />
<meta property="og:description" content={description} />
<meta property="og:site_name" content="Tuono" />
<meta property="og:locale" content="en_US" />
</Head>
)
}
@@ -0,0 +1,3 @@
import NavigationButtons from './navigation-buttons'
export default NavigationButtons
@@ -0,0 +1,62 @@
import { Box, Button, Text, Title, Flex } from '@mantine/core'
import { Link } from 'tuono'
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
interface NavigationButton {
href: string
title: string
}
interface NavigationButtonsProps {
prev?: NavigationButton
next?: NavigationButton
}
export default function NavigationButtons({
prev,
next,
}: NavigationButtonsProps): JSX.Element {
return (
<Flex mt={50} gap={10}>
{prev && <NavigationBtn type="prev" {...prev} />}
{next && <NavigationBtn type="next" {...next} />}
</Flex>
)
}
interface NavigationButtonProps extends NavigationButton {
type: 'next' | 'prev'
}
const NavigationBtn = ({
type,
title,
href,
}: NavigationButtonProps): JSX.Element => {
const heading = type === 'next' ? 'Next' : 'Previous'
const textAlign = type === 'next' ? 'left' : 'right'
const variant = type === 'next' ? 'filled' : 'outline'
return (
<Button
component={Link}
fullWidth
variant={variant}
href={href}
justify="space-between"
h="auto"
rightSection={type === 'next' && <IconArrowRight />}
leftSection={type === 'prev' && <IconArrowLeft />}
p="20"
>
<Box>
<Title component="span" display="block" order={4} style={{ textAlign }}>
{heading}
</Title>
<Text component="span" display="block" style={{ textAlign }}>
{title}
</Text>
</Box>
</Button>
)
}
+1 -1
View File
@@ -86,8 +86,8 @@ export default function RootRoute({ children }: RootRouteProps): JSX.Element {
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<ColorSchemeScript />
</Head>
<ColorSchemeScript />
<MantineProvider theme={theme}>
<AppShell
header={{ height: 60 }}
@@ -1,14 +1,14 @@
import { Head } from 'tuono'
import MetaTags from '../../components/meta-tags'
<Head>
<title>Tuono - CLI</title>
</Head>
<MetaTags
title="Tuono - CLI"
canonical="https://tuono.dev/documentation/cli"
description="Tuono is the CLI that provides all the needed commands to handle the full-stack project."
/>
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
<Breadcrumbs>
<Element label="CLI" />
</Breadcrumbs>
<Breadcrumbs breadcrumbs={[{ label: 'CLI' }]} />
# CLI
@@ -1,14 +1,14 @@
import { Head } from 'tuono'
import MetaTags from '../../components/meta-tags'
<Head>
<title>Tuono - Contributing</title>
</Head>
<MetaTags
title="Tuono - Contributing"
canonical="https://tuono.dev/documentation/contributing"
description="The project is massive - if you like it, do consider contributing!"
/>
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
<Breadcrumbs>
<Element label="✨ Contributing" />
</Breadcrumbs>
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
# Contributing
@@ -1,29 +1,31 @@
import { Head } from 'tuono'
import MetaTags from '../../components/meta-tags'
<Head>
<title>Tuono - Documentation</title>
</Head>
<MetaTags
title="Tuono - Documentation"
canonical="https://tuono.dev/documentation"
description="Tuono is a full-stack framework for building React applications using Rust as the backend."
/>
import Breadcrumbs from '../../components/breadcrumbs'
<Breadcrumbs></Breadcrumbs>
<Breadcrumbs />
# Tuono
**Tuono is a full-stack framework for building React applications using Rust as the
backend.**
Tuono is a full-stack framework for building React applications using Rust as the
backend.
Tuono (Italian word for "thunder", pronounced /2 Oh No/). Why Tuono? Just a badass name.
## Introduction
**NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)**
NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)
Tuono is a full-stack React framework with the server side written in Rust.
Because of this, Tuono is extremely fast and, the requests are handled by a multithreaded Rust server.
React is still React - it is just superpowered.
**Rust is a hard language, then writing server side code is hard as well, right? (no again)**
Rust is a hard language, then writing server side code is hard as well, right? (no again)
Tuono provides a collection of utilities to handle the server side code seamlessly with
the React code. Each server side route is managed in a separate file alongside the React route.
@@ -1,13 +1,14 @@
import { Head } from 'tuono'
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
<Head>
<title>Tuono - Installation</title>
</Head>
import MetaTags from '../../components/meta-tags'
<Breadcrumbs>
<Element label="Installation" />
</Breadcrumbs>
<MetaTags
title="Tuono - Installation"
canonical="https://tuono.dev/documentation/installation"
description="Tuono is a development environment built in rust and typescript that outputs a website written in both the languages"
/>
<Breadcrumbs breadcrumbs={[{ label: 'Installation' }]} />
# Installation
@@ -1,14 +1,13 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tuono - Routing</title>
</Head>
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/intro"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Routing" />
</Breadcrumbs>
<Breadcrumbs breadcrumbs={[{ label: 'Routing' }]} />
# Routing index
@@ -1,15 +1,18 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tuono - Routing</title>
</Head>
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Routing" href="/documentation/routing" />
<Element label="Project structure" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Routing', href: '/documentation/routing' },
{ label: 'Project structure' },
]}
/>
# Project structure
@@ -1,15 +1,19 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tutorial - API fetching</title>
</Head>
<MetaTags
title="Tuono - API fetching"
canonical="https://tuono.dev/documentation/tutorial/api-fetching"
description="Learn how to fetch remote APIs on the backend"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" href="/documentation/tutorial" />
<Element label="API fetching" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Tutorial', href: '/documentation/tutorial' },
{ label: 'API fetching' },
]}
/>
# API fetching
@@ -103,3 +107,13 @@ export default function IndexPage({
```
Refresh the browser now! A bit ugly, but all the Pokémon are finally printed on screen!
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
prev={{
title: 'Development setup',
href: '/documentation/tutorial/development-setup',
}}
next={{ title: 'Components', href: '/documentation/tutorial/components' }}
/>
@@ -1,15 +1,19 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tutorial - Components</title>
</Head>
<MetaTags
title="Tuono - Components"
canonical="https://tuono.dev/documentation/tutorial/components"
description="Learn how to manage the components in a Tuono codebase"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" href="/documentation/tutorial" />
<Element label="Components" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Tutorial', href: '/documentation/tutorial' },
{ label: 'Components' },
]}
/>
# Components
@@ -127,3 +131,13 @@ export default function PokemonLink({
);
}
```
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
prev={{ title: 'API fetching', href: '/documentation/tutorial/api-fetching' }}
next={{
title: 'Dynamic routes',
href: '/documentation/tutorial/dynamic-routes',
}}
/>
@@ -1,15 +1,19 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tutorial - Conclusion</title>
</Head>
<MetaTags
title="Tuono - Conclusion"
canonical="https://tuono.dev/documentation/tutorial/conclusion"
description="You just created a multi thread full stack application with Rust and React"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" href="/documentation/tutorial" />
<Element label="Conclusion" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Tutorial', href: '/documentation/tutorial' },
{ label: 'Conclusion' },
]}
/>
# Conclusion
@@ -22,3 +26,12 @@ As I mentioned in the introduction, I'd love to hear what you thought about the
at [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it) or in Twitter (X) DMs [@valerioageno](https://twitter.com/valerioageno).
Ciao
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
prev={{
title: 'Production build',
href: '/documentation/tutorial/production',
}}
/>
@@ -1,15 +1,19 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tutorial - Development setup</title>
</Head>
<MetaTags
title="Tuono - Development setup"
canonical="https://tuono.dev/documentation/tutorial/development-setup"
description="Learn how to setup a new Tuono project"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" href="/documentation/tutorial" />
<Element label="Development setup" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Tutorial', href: '/documentation/tutorial' },
{ label: 'Development setup' },
]}
/>
# Development setup
@@ -64,3 +68,10 @@ The first time might take a little bit because it will install all the Rusts
> In case you face any error delete the cache `.tuono` folder and run it again!
Then open [http://localhost:3000/](http://localhost:3000) on the browser.
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
prev={{ title: 'Tutorial', href: '/documentation/tutorial' }}
next={{ title: 'API fetching', href: '/documentation/tutorial/api-fetching' }}
/>
@@ -1,15 +1,19 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tutorial - Dynamic routes</title>
</Head>
<MetaTags
title="Tuono - Dynamic routes"
canonical="https://tuono.dev/documentation/tutorial/dynamic-routes"
description="Learn how to catch more endpoint with the same route loading dynamic data"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" href="/documentation/tutorial" />
<Element label="Dynamic routes" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Tutorial', href: '/documentation/tutorial' },
{ label: 'Dynamic routes' },
]}
/>
# Dynamic routes
@@ -157,3 +161,13 @@ export default function PokemonView({
font-weight: 700;
}
```
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
prev={{ title: 'Components', href: '/documentation/tutorial/components' }}
next={{
title: 'Error handling',
href: '/documentation/tutorial/error-handling',
}}
/>
@@ -1,15 +1,19 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tutorial - Error handling</title>
</Head>
<MetaTags
title="Tuono - Error handling"
canonical="https://tuono.dev/documentation/tutorial/error-handling"
description="Learn how to handle the server side error on your Tuono project"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" href="/documentation/tutorial" />
<Element label="Error handling" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Tutorial', href: '/documentation/tutorial' },
{ label: 'Error handling' },
]}
/>
# Error handling
@@ -100,3 +104,13 @@ async fn get_all_pokemons(_req: Request, fetch: Client) -> Response {
If you now try to load a not-existing Pokémon (`http://localhost:3000/pokemons/tuono-pokemon`) you will
correctly receive a 404 status code in the console.
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
prev={{
title: 'Dynamic routes',
href: '/documentation/tutorial/dynamic-routes',
}}
next={{ title: 'SEO and meta tags', href: '/documentation/tutorial/seo' }}
/>
@@ -1,14 +1,14 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tuono - Tutorial</title>
</Head>
<MetaTags
title="Tuono - Tutorial"
canonical="https://tuono.dev/documentation/tutorial"
description="This tutorial is meant to give you a sneak peek at the framework and is intended to evolve during the development - be sure to have installed the latest version"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" />
</Breadcrumbs>
<Breadcrumbs breadcrumbs={[{ label: 'Tutorial' }]} />
# Tutorial
@@ -26,3 +26,12 @@ $ tuono new tutorial --template tutorial
> I'd love to hear your thoughts about the framework and the tutorial - feel free to reach me at [valerioageno@yahoo.it](mailto:valerioageno@ahoo.it)
> or on Twitter (X) DM [@valerioageno](https://twitter.com/valerioageno)
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
next={{
title: 'Development setup',
href: '/documentation/tutorial/development-setup',
}}
/>
@@ -1,15 +1,19 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tutorial - Building for production</title>
</Head>
<MetaTags
title="Tuono - Building for production"
canonical="https://tuono.dev/documentation/tutorial/production"
description="Learn how to build the production project and run the optimize version"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" href="/documentation/tutorial" />
<Element label="Production build" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Tutorial', href: '/documentation/tutorial' },
{ label: 'Production build' },
]}
/>
# Production build
@@ -31,3 +35,13 @@ Check again [`http://localhost:3000/`](http://localhost:3000/) This environment
optimizations ready to unleash the power of a rust server that seamlessly renders a React application!🚀
> Note: The `out` directory is not standalone. You can't rely just on it to run the production server.
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
prev={{
title: 'Server redirection',
href: '/documentation/tutorial/redirections',
}}
next={{ title: 'Conclusion', href: '/documentation/tutorial/conclusion' }}
/>
@@ -1,15 +1,19 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tutorial - Redirections</title>
</Head>
<MetaTags
title="Tuono - Redirection"
canonical="https://tuono.dev/documentation/tutorial/redirection"
description="Learn how to redirect to different route from the server"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" href="/documentation/tutorial" />
<Element label="Redirections" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Tutorial', href: '/documentation/tutorial' },
{ label: 'Redirections' },
]}
/>
# Redirections
@@ -45,3 +49,13 @@ Now let's create the button in the home page to actually point to it!
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!
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
prev={{ title: 'SEO and meta tags', href: '/documentation/tutorial/seo' }}
next={{
title: 'Production build',
href: '/documentation/tutorial/production',
}}
/>
@@ -1,15 +1,19 @@
import { Head } from 'tuono'
import MetaTags from '../../../components/meta-tags'
<Head>
<title>Tutorial - SEO and meta tags</title>
</Head>
<MetaTags
title="Tuono - SEO and meta tags"
canonical="https://tuono.dev/documentation/tutorial/eeo"
description="Learn how to handle meaningful meta tags for your Tuono project"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs>
<Element label="Tutorial" href="/documentation/tutorial" />
<Element label="SEO and meta tags" />
</Breadcrumbs>
<Breadcrumbs
breadcrumbs={[
{ label: 'Tutorial', href: '/documentation/tutorial' },
{ label: 'SEO and meta tags' },
]}
/>
# SEO and meta tags
@@ -94,3 +98,16 @@ export default function Pokemon({ data }: TuonoProps): JSX.Element {
```
The `Head` component takes as children any valid HTML meta tag.
import NavigationButtons from '../../../components/navigation-buttons'
<NavigationButtons
prev={{
title: 'Error handling',
href: '/documentation/tutorial/error-handling',
}}
next={{
title: 'Server redirection',
href: '/documentation/tutorial/redirections',
}}
/>
+6 -8
View File
@@ -1,16 +1,14 @@
import { Head } from 'tuono'
import Hero from '../components/hero'
import MetaTags from '../components/meta-tags'
export default function IndexPage(): JSX.Element {
return (
<>
<Head>
<title>Tuono - The React/Rust full-stack framework</title>
<meta
name="description"
content="The technologies we love seamlessly working together to unleash the highest web performance ever met on React"
/>
</Head>
<MetaTags
title="Tuono - The React/Rust full-stack framework"
canonical="https://tuono.dev"
description="The technologies we love seamlessly working together to unleash the highest web performance ever met on React"
/>
<Hero />
</>
)
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tuono"
version = "0.10.4"
version = "0.11.2"
edition = "2021"
authors = ["V. Ageno <valerioageno@yahoo.it>"]
description = "The react/rust fullstack framework"
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tuono_lib"
version = "0.10.4"
version = "0.11.2"
edition = "2021"
authors = ["V. Ageno <valerioageno@yahoo.it>"]
description = "The react/rust fullstack framework"
@@ -33,7 +33,7 @@ either = "1.13.0"
tower-http = {version = "0.6.0", features = ["fs"]}
colored = "2.1.0"
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.10.4"}
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.11.2"}
# Match the same version used by axum
tokio-tungstenite = "0.24.0"
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tuono_lib_macros"
version = "0.10.4"
version = "0.11.2"
edition = "2021"
description = "The react/rust fullstack framework"
keywords = [ "react", "typescript", "fullstack", "web", "ssr"]
@@ -2,21 +2,21 @@ import { Head, type TuonoProps } from 'tuono'
import PokemonView from '../../components/PokemonView'
interface Pokemon {
name: string
id: string
weight: number
height: number
name: string
id: string
weight: number
height: number
}
export default function PokemonPage({
data,
data,
}: TuonoProps<Pokemon>): JSX.Element {
return (
<>
<Head>
<title>Pokemon: {data?.name}</title>
</Head>
<PokemonView pokemon={data} />
</>
)
return (
<>
<Head>
<title>{`Pokemon: ${data?.name}`}</title>
</Head>
<PokemonView pokemon={data} />
</>
)
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "workspace",
"packageManager": "pnpm@9.1.1",
"packageManager": "pnpm@9.12.3+sha256.24235772cc4ac82a62627cd47f834c72667a2ce87799a846ec4e8e555e2d4b8b",
"scripts": {
"dev": "turbo watch dev",
"build": "turbo build --filter tuono",
@@ -21,7 +21,7 @@
"license": "MIT",
"devDependencies": {
"@tanstack/config": "^0.7.0",
"@types/node": "^20.12.7",
"@types/node": "^22.0.0",
"@types/react": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "tuono-fs-router-vite-plugin",
"version": "0.10.4",
"version": "0.11.2",
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
"scripts": {
"dev": "vite build --watch",
@@ -21,6 +21,7 @@ import { ROUTES_FOLDER, ROOT_PATH_ID, GENERATED_ROUTE_TREE } from './constants'
import { format } from 'prettier'
import { sortRouteNodes } from './sort-route-nodes'
import isDefaultExported from './utils/is-default-exported'
let latestTask = 0
@@ -50,6 +51,13 @@ async function getRouteNodes(
if (dirent.isDirectory()) {
await recurse(relativePath)
} else if (fullPath.match(/\.(tsx|ts|jsx|js|mdx)$/)) {
// Check that the route is correctly default exported
if (
fullPath.match(/\.(tsx|ts|jsx|js)$/) &&
!isDefaultExported((await fsp.readFile(fullPath)).toString())
) {
return
}
const filePath = replaceBackslash(path.join(dir, dirent.name))
const filePathNoExt = removeExt(filePath)
let routePath =
@@ -0,0 +1,4 @@
export default function isDefaultExported(source: string): boolean {
const regex = new RegExp('export default')
return regex.test(source)
}
@@ -1 +1,2 @@
/** */
// export default
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
@@ -1 +1 @@
/** */
/** export default */
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "tuono-lazy-fn-vite-plugin",
"version": "0.10.4",
"version": "0.11.2",
"description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework",
"scripts": {
"dev": "vite build --watch",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "tuono-router",
"version": "0.10.4",
"version": "0.11.2",
"description": "React routing component for the framework tuono. Tuono is the react/rust fullstack framework",
"scripts": {
"dev": "vite build --watch",
+9 -2
View File
@@ -5,13 +5,20 @@ import useRoute from '../hooks/useRoute'
import { useInView } from 'react-intersection-observer'
interface TuonoLinkProps {
/**
* If "true" the route gets loaded when the link enters the viewport. Default "true"
*/
preload?: boolean
/**
* If "false" the scroll offset will be kept across page navigation. Default "true"
*/
scroll?: boolean
}
export default function Link(
componentProps: AnchorHTMLAttributes<HTMLAnchorElement> & TuonoLinkProps,
): JSX.Element {
const { preload = true, ...props } = componentProps
const { preload = true, scroll = true, ...props } = componentProps
const router = useRouter()
const route = useRoute(props.href)
const { ref } = useInView({
@@ -30,7 +37,7 @@ export default function Link(
return
}
router.push(props.href || '')
router.push(props.href || '', { scroll })
}
return (
+14 -2
View File
@@ -1,4 +1,5 @@
import * as React from 'react'
import type { ReactElement, ComponentType } from 'react'
import type { RouteComponent } from './types'
type ImportFn = () => Promise<{ default: React.ComponentType<any> }>
@@ -40,7 +41,18 @@ export const dynamic = (importFn: ImportFn): JSX.Element => {
}
export const lazyLoadComponent = (factory: ImportFn): RouteComponent => {
const Component = React.lazy(factory) as unknown as RouteComponent
Component.preload = factory
let LoadedComponent: ComponentType<any> | undefined
const LazyComponent = React.lazy(factory) as unknown as RouteComponent
const loadComponent = (): Promise<void> =>
factory().then((module) => {
LoadedComponent = module.default
})
const Component = (props: any): ReactElement =>
React.createElement(LoadedComponent || LazyComponent, props)
Component.preload = loadComponent
return Component
}
+14 -2
View File
@@ -1,10 +1,17 @@
import { useRouterStore } from './useRouterStore'
interface PushOptions {
/**
* If "false" the scroll offset will be kept across page navigation. Default "true"
*/
scroll?: boolean
}
interface UseRouterHook {
/**
* Redirects to the path passed as argument updating the browser history.
*/
push: (path: string) => void
push: (path: string, opt?: PushOptions) => void
/**
* This object contains all the query params of the current route
@@ -23,7 +30,8 @@ export const useRouter = (): UseRouterHook => {
st.updateLocation,
])
const push = (path: string): void => {
const push = (path: string, opt?: PushOptions): void => {
const { scroll = true } = opt || {}
const url = new URL(path, window.location.origin)
updateLocation({
@@ -34,6 +42,10 @@ export const useRouter = (): UseRouterHook => {
hash: url.hash,
})
history.pushState(path, '', path)
if (scroll) {
window.scroll(0, 0)
}
}
return {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "tuono",
"version": "0.10.4",
"version": "0.11.2",
"description": "The react/rust fullstack framework",
"scripts": {
"dev": "vite build --watch",
@@ -84,10 +84,10 @@
"@babel/types": "^7.24.0",
"@mdx-js/rollup": "^3.0.1",
"@types/babel__core": "^7.20.5",
"@types/node": "^20.12.7",
"@types/node": "^22.0.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"fast-text-encoding": "^1.0.6",
"react-meta-tags": "^1.0.1",
"react-helmet-async": "^2.0.5",
"tuono-fs-router-vite-plugin": "workspace:*",
"tuono-lazy-fn-vite-plugin": "workspace:*",
"tuono-router": "workspace:*",
+4 -1
View File
@@ -1,6 +1,7 @@
import React from 'react'
import { hydrateRoot } from 'react-dom/client'
import { RouterProvider, createRouter } from 'tuono-router'
import { HelmetProvider } from 'react-helmet-async'
type RouteTree = any
@@ -14,7 +15,9 @@ export function hydrate(routeTree: RouteTree): void {
hydrateRoot(
rootElement,
<React.StrictMode>
<RouterProvider router={router} />
<HelmetProvider>
<RouterProvider router={router} />
</HelmetProvider>
</React.StrictMode>,
)
}
+1 -3
View File
@@ -1,4 +1,4 @@
import Head from 'react-meta-tags'
export { Helmet as Head } from 'react-helmet-async'
export {
createRoute,
@@ -11,6 +11,4 @@ export {
useRouter,
} from 'tuono-router'
export { Head }
export type { TuonoProps } from './types'
+13 -10
View File
@@ -1,8 +1,8 @@
import 'fast-text-encoding' // Mandatory for React18
import * as React from 'react'
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import MetaTagsServer from 'react-meta-tags/server'
import { MetaTagsContext } from 'react-meta-tags'
import type { HelmetServerState } from 'react-helmet-async'
import { HelmetProvider } from 'react-helmet-async'
import { RouterProvider, createRouter } from 'tuono-router'
type RouteTree = any
@@ -44,23 +44,26 @@ export function serverSideRendering(routeTree: RouteTree) {
const cssBundles = props.cssBundles as string[]
const router = createRouter({ routeTree }) // Render the app
const metaTagsInstance = MetaTagsServer()
const helmetContext = {}
const app = renderToString(
<MetaTagsContext extract={metaTagsInstance.extract}>
<HelmetProvider context={helmetContext}>
<RouterProvider router={router} serverProps={props} />
</MetaTagsContext>,
</HelmetProvider>,
)
const metaTags = metaTagsInstance.renderToString()
const { helmet } = helmetContext as { helmet: HelmetServerState }
return `<!doctype html>
<html>
<html ${helmet.htmlAttributes.toString()}>
<head>
${metaTags}
${helmet.title.toString()}
${helmet.priority.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
${helmet.script.toString()}
${generateCssLinks(cssBundles, mode)}
</head>
<body>
<body ${helmet.bodyAttributes.toString()}>
<div id="__tuono">${app}</div>
${renderToStaticMarkup(
<script