Wat is @tanstack/start?

@tanstack/start is een project vanuit de TanStack die React uitbreid. Het is een zo genoemd react meta framework dat React uitbreid met features die het standaard niet in zich heeft. Zo breidt het React als het ware verder uit.

Van React weten we dat het zichzelf geen framework maar een library noemt. Je kan hier vraagtekens bij zetten, echter hierdoor mist React een aantal belangrijke features:

  • Een router, om URLs aan componenten te koppelen

  • Een build mechanisme: via het vite build commando output een kant en klaar te serveren folder uit, vite dev start een dev server.

  • Een manier om React server side te runnen met support voor React server components, wel met een @tankstack twist.

Het zijn deze missende features die @tanstack/start opvult!

@tanstack/router

@tankstack/start bouwt verder op een TypeScript gebaseerde router genaamd @tankstack/router. Met een router kan je eenvoudig URLs aan componenten koppelen.

De @tankstack/router werkt op basis van files / bestanden, een file based router dus. Wanneer je in de folder genaamd routes een /over/index.tsx bestand aanmaakt dan krijg je een "/over" pagina.

Dynamische routes maak je aan door een $ teken te gebruiken in een folder: /pokemon/$pokemonId/index.tsx. Dit matched dan op /pokemon/1 en op /pokemon/2, maar ook op /pokemon/aap

Een index.tsx bestand moet een export const Route hebben waarin je de route configureerd:

export const Route = createFileRoute('/about/')({
  component: About
});

function About() {
  return <p>About</p>
}

Het mooie aan @tankstack/router is dat alles echt typesafe wordt via TypeScript de query / search parameters en alle path parameters worden gecontroleerd! Je kan dus niet per ongeluk linken naar iets dat niet bestaat.

Via Zod kan je alles laten checken. Hier een voorbeeld dat eist dat er een page en query search params zijn:

const pokemonListSearchSchema = z.object({
  page: z.number().catch(1),
  query: z.string().catch('')
});

export const Route = createFileRoute('/pokedex/')({
  validateSearch: pokemonListSearchSchema,
  search: {
    middlewares: [stripSearchParams(pokemonListSearchSchema.parse({}))]
  },

  component: PokemonList

Ook kent @tanstack/start het concept "loaders", een loader haalt data op van een back-end voordat het component wordt gerendered. Dit werkt dan ook weer met @tanstack/query:

export const Route = createFileRoute('/pokedex/')({
  loaderDeps: ({ search }) => search,
  loader: ({ deps, context }) => {
    context.queryClient.ensureQueryData(
      getPokemonsOptions({ ...deps, size: PAGE_SIZE })
    );
  },

  component: PokemonList
});

Er is ook ondersteuning voor error routes, 404 / not found routes, herbruikbare layouts en nog veel meer andere features.

Isomorphic rendering

@tanstack/start rendered alles "Isomorphic": dit betekent dat alles eerst op de server wordt gerendered. Vervolgens gaat de client side / browser er weer mee verder en maakt het dynamisch.

Het renderen op een server is goed Search Engine Optimalization (SEO) complete HTML sturen is beter dan wanneer JavaScript noodzakelijk is. Crawlers zoals Google willen het liefst HTML indexeren. Als voordeel werkt je pagina ook beter zonder JavaScript.

Een klein beetje JavaScript is noodzakelijk om de clientside navigatie naar nieuwe pagina's te laten werken.

React Server Components (RSC)

Het is op dit moment "experimental" maar @tanstack/start is bezig om componenten te maken die alleen op de server leven, en niet op de client / browser.

Neem dit voorbeeld van de @tanstack/start website:

import { createServerFn } from '@tanstack/react-start'
import {
  createFromReadableStream,
  renderToReadableStream,
} from '@tanstack/react-start/rsc'

// Create a server function
const getGreeting = createServerFn().handler(async () => {
  // Create an RSC readable stream
  return renderToReadableStream(
    // Return JSX
    <h1>Hello from the server</h1>,
  )
})

function Greeting() {
  const query = useQuery({
    queryKey: ['greeting'],
    queryFn: async () =>
      // Create a renderable element from the stream
      createFromReadableStream(
        // Call our server function to get the stream
        await getGreeting(),
      ),
  })

  // Render!
  return <>{query.data}</>
}

De getGreeting kan je dus eigenlijk zien als een component / JSX die alleen op de server gerunned wordt. Aan de client / browser kant is dit component compleet statisch. React hoeft het component dus niet client side nog te renderen.

RSCs runnen dus niet isomorphic!

Vergeleken met Next.js zijn RSC implementatie voelt het voor mij allemaal wat minder gelikt. Het is allemaal nog experimenteel, dus de API zal mogelijk nog veranderen.

Wat wel leuk is om te zien is dat er verschillend over RSC wordt gedacht. Waar Next.js magie omarmt, kiest @tanstack/start toch meer voor een expliciete API.

@tanstack/start modussen

@tanstack/start heeft twee modussen:

  • De SSR (Server Side Rendering) mode (en de default): in deze modus zal @tanstack/start on the fly dynamische onderdelen van je website renderen. Maar zal wel proberen zo veel mogelijk puur statisch aan te maken op build time.

    Een server side JavaScript runtime is dan wel noodzakelijk. Denk aan Node.js, bun of Deno.

    Ook is het mogelijk om via Incremental Static Regeneration (ISR) de server af en toe opnieuw pagina's te laten genereren. Hiermee kan je een middenweg bewandelen: statische pagina's die één in de zoveel tijd worden ververst.

    Qua hosting heeft @tankstack/start een aantal verschillende opties. Je zit dus niet snel vast aan één service.

  • De SPA mode voor een Single Page Application die geen JavaScript runtime op de server nodig heeft.

    vite build levert dan een productie versie op die betaat uit statische files. Een statische file is een file die nooit hoeft te veranderen! Iedere gebruiker krijgt precies hetzelfde, dit maakt het extreem agressief te cachen!

    Een JavaScript runtime is dan niet nodig een CDN (Content Delivery Network), of een simpele webserver zoals apache of nginx is dan voldoende om het te serveren.

    Natuurlijk kan je in deze modus nog steeds calls maken naar je back-end REST / GraphQL API, en zo dynamisch content geven aan je gebruiker. Alleen de JavaScript / HTML / CSS is dan statisch.

    De SPA modus is niet de default en dien je aan te zetten.

Wat vindt Maarten?

Een foto van Maarten, een gemiddeld gezicht al zeg ik het zelf.

@tankstack/start heeft veel potentie. Wel zie ik dat het nog een ruw diamantje is, het is niet voor niets nog in RC (Release Candidate). De documentatie is soms matig, en er missen wat voorbeelden.

@tankstack/start is geschikt voor de avonturier die erg van TypeScript houdt.

Contact

Neem contact op via e-mail, phone of app, als je vragen hebt of een offerte wilt aanvragen.

Vraag een offerte aan Boek kennismakingsgesprek