Skip to content

Commit 2f2a0da

Browse files
committed
update 06
1 parent 0e8fd32 commit 2f2a0da

File tree

5 files changed

+57
-63
lines changed

5 files changed

+57
-63
lines changed

06-tanstack-start/README.md

Lines changed: 38 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,24 @@
11
# Tanstack Start
22

3-
Ahora le toca el turno a [`Tanstack Start`](https://tanstack.com/start/latest), que es el framework para transformar nuestra aplicación en una de `Server Side Rendering` (ojo que por ahora esta en `beta` y para React y para Solid).
3+
Si arrancamos el proyecto, vemos que utiliza `vinxi` (aunque en la versión release lo van a sustituir por un `plugin` de `vite`)
44

5-
## Instalación
5+
Si abrimos las DevTools del navegador y cargamos la primera página, vemos que directamente viene un `html` con el contenido de ésta.
66

7-
Las librerías que ahora necesitamos, a parte de la del router que ya tenemos instalada, son:
7+
Si hacemos `login` y navegamos vemos que ahora hace la descarga con las rutas `lazy` como si fuese una SPA normal.
88

9-
```bash
10-
npm install @tanstack/react-start
11-
npm install -D vinxi
9+
Pero si recargamos la página, vemos que vuelve a hacer la descarga del `html` completo aunque no sea la del `login`.
1210

13-
```
14-
15-
> Vinxi es un SDK para crear tu propio framework como `Tanstack Start`, `Next.js`, etc. con tus propias reglas y está basado en `Vite`, `Nitro` y `Rollup`.
16-
>
17-
> Pero comentan que Vinxi la eliminarán en la versión 1 y lo sustituirán por un `plugin` de `Vite` o directamente con el `CLI`.
11+
Así cumplimos con el SEO y mantenemos lo bueno de una SPA.
1812

19-
¿Qué dependencias no necesitamos ahora?
13+
> Podemos poner breakpoint en el VSCode y en el navegador.
2014
21-
```bash
22-
npm uninstall vite @vitejs/plugin-react @tanstack/router-plugin
23-
```
15+
## Configuración
2416

25-
> No necesitamos estas porque ya están incluidas en `Tanstack Start`.
17+
Para configurar todo esto, necesitamos hacer unos pequeños ajustes:
2618

27-
## Configuración
19+
### package.json
2820

29-
Es decir, ahora necesitamos hacer unos pequeños ajustes para que nuestra aplicación funcione con `SSR`.
21+
- Instalamos `@tanstack/react-start` y `vinxi`, ya tienen integrados `vite` y el `plugin` del router.
3022

3123
_./frontend/package.json_
3224

@@ -40,38 +32,11 @@ _./frontend/package.json_
4032
},
4133
```
4234

43-
Renombramos el fichero `vite.config.ts` a `app.config.ts`.
35+
### app.config.ts
4436

45-
- Ahora `defineConfig` lo importamos de `@tanstack/react-start/config`.
46-
- Los plugins de `react` y el `router` ya no los necesitamos, porque ya están incluidos, y el router se puede configurar en la propiedad `tsr` (TanStack Router).
47-
- Eso si, le añadimos la propiedad `appDirectory` a `src` para que sepa donde está nuestra aplicación (por defecto es `app`).
48-
- Y la configuración restante, en la propia de `vite`.
37+
Renombramos el fichero `vite.config.ts` a `app.config.ts` y como veis la configuración es muy parecida, y sigue soportando la configuración de `vite`.
4938

50-
_./frontend/vite.config.ts_
51-
52-
```diff
53-
- import { defineConfig } from "vite";
54-
+ import { defineConfig } from "@tanstack/react-start/config";
55-
- import react from "@vitejs/plugin-react";
56-
- import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
57-
...
58-
59-
export default defineConfig({
60-
- plugins: [
61-
- TanStackRouterVite({ target: "react", autoCodeSplitting: true }),
62-
- tailwindcss(),
63-
- react(),
64-
- ],
65-
+ tsr: {
66-
+ target: "react",
67-
+ autoCodeSplitting: true,
68-
+ appDirectory: "src",
69-
+ },
70-
+ vite: {
71-
+ plugins: [tailwindcss()],
72-
+ },
73-
});
74-
```
39+
### router config
7540

7641
Con respecto a la instancia del router, la tenemos que exponer en forma de función porque se va a utilizar en servidor y en cliente:
7742

@@ -97,9 +62,11 @@ declare module "@tanstack/react-router" {
9762
}
9863
```
9964

100-
Eso significa, que ahora vamos a tener 2 puntos de entrada: uno para el cliente y otro para el servidor.
65+
### ssr.tsx
66+
67+
Eso significa, que ahora vamos a tener 2 puntos de entrada:
10168

102-
El de servidor `ssr.tsx`:
69+
Uno para el servidor `ssr.tsx`:
10370

10471
- Importamos la funcion de `createRouter` y se la pasamos al `createStartHandler` que será el encargado de manejar las peticiones según la ruta que se solicite.
10572
- También importamos la función `getRouterManifest` que se encargará de gestionar los recursos y `preloads` de la aplicación.
@@ -122,7 +89,9 @@ export default createStartHandler({
12289
})(defaultStreamHandler);
12390
```
12491

125-
Para la parte de cliente:
92+
### client.tsx
93+
94+
Y otro para el cliente (`client.tsx`):
12695

12796
- Al igual que antes, importamos la función `createRouter`.
12897
- Creamos la instancia, y se la pasamos al componente `StartClient` que es el encargado de manejar las peticiones en cliente.
@@ -140,9 +109,13 @@ const router = createRouter();
140109
hydrateRoot(document, <StartClient router={router} />);
141110
```
142111

143-
Es decir, ahora nos sobran 2 ficheros: el `index.html` y el `index.tsx`. Tenemos que ir migrando el contenido.
112+
### Migración del index.html
113+
114+
Ahora nos sobran 2 ficheros: el `index.html` y el `index.tsx`. Para mover el código a un sitio común podríamos optar por el fichero principal del router `__root.tsx`:
115+
116+
- Utilizar propiedad `head` del router para cambiar título y metadatos.
117+
- Añadimos la estructura básica de un `html` en el render.
144118

145-
Ahora, el fichero principal de la aplicación tanto para el cliente como para el servidor es el `routes/__root.tsx`:
146119

147120
_./frontend/src/routes/\_\_root.tsx_
148121

@@ -175,7 +148,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
175148
function RootComponent() {
176149
return (
177150
- <>
178-
+ <html lang="en">
151+
+ <html lang="en" data-theme="corporate">
179152
+ <head>
180153
+ <HeadContent />
181154
+ </head>
@@ -191,19 +164,17 @@ function RootComponent() {
191164

192165
```
193166

194-
> Ya podemos borrar el `index.html`
167+
### Migración del index.tsx
195168

196-
Vamos ahora con el `index.tsx`:
169+
Y para el `index.tsx`:
197170

198-
- Nos llevamos los imports de los estilos, pero lo importamos como una URL para poder inyectarlos en el `head` del html como un `link` para que funcionen en modo servidor.
199-
- El `RouterProvider` es algo que ya está incluido en los puntos de entrada de servidor y cliente.
200-
- Pero si nos vamos a llevar la configuración de `react-query`. Si se necesita en un nivel superior, se puede usar [las propiedas `Wrap` o `InnerWrap` del router](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#wrap-property)
201-
- Ya podemos borar el `index.tsx`.
171+
- Podemos importar los estilos, pero como un `link` de HTML para que funcionen en modo servidor.
172+
- Nos llevamos los proveedor necesarios (menos el del router que ya lo tiene incluido internamente),
173+
- Y ya estaría todo listo.
202174

203175
_./frontend/src/routes/\_\_root.tsx_
204176

205177
```diff
206-
+ import appStyles from "../index.css?url";
207178
import {
208179
Outlet,
209180
createRootRouteWithContext,
@@ -256,6 +227,10 @@ function RootComponent() {
256227

257228
> Nota: [CSS Modules FOUC (flash of unstyled content) se resolverá en la versión 1](https://github.com/TanStack/router/issues/3023#issuecomment-2689163745)
258229
259-
Ahora, sea cual sea la ruta que solicitemos inicialmente, se renderizará en servidor. Y las navegaciones posteriores se harán en cliente, con esto tenemos lo mejor de ambos mundos, conseguimos una carga inicial rápida y mantenemos la interactividad de una SPA (con su memoria en cliente, etc).
230+
### Otros cambios
260231

261-
> Podemos poner breakpoint en el VSCode y en el navegador.
232+
Eso si, si tienes código como por ejemplo lo que yo he añadido del [localStorage](./frontend/src/common/storage.ts) tendrás que comprobar si se ejecuta en cliente o servidor.
233+
234+
### Conclusión
235+
236+
A partir de aquí ya tenemos una aplicación con `SSR` y te puedes aprovechar las nuevas características que te da de `Server Functions`, `API Routes`, etc.

06-tanstack-start/api/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ app.post("/api/logout", async (context) => {
2929
return context.body(null, 204);
3030
});
3131

32+
app.get("/api/auth-user", async (context) => {
33+
return context.json(db.loggedUser);
34+
});
35+
3236
app.get("/api/posts", async (context) => {
3337
return context.json(db.posts);
3438
});

06-tanstack-start/frontend/src/common/api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export const logout = async (): Promise<void> => {
1717
await fetch(`${API_URL}/logout`, { method: "POST" });
1818
};
1919

20+
export const getAuthUser = async (): Promise<string | null> =>
21+
fetch(`${API_URL}/auth-user`).then((response) => response.json());
22+
2023
const getPosts = async (): Promise<model.Post[]> =>
2124
await fetch(`${API_URL}/posts`).then((res) => res.json());
2225

06-tanstack-start/frontend/src/common/auth.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface Auth {
66
user: string | null;
77
login: (email: string, password: string) => Promise<void>;
88
logout: () => Promise<void>;
9+
fetchAuthUser: () => Promise<void>;
910
}
1011

1112
export const auth: Auth = {
@@ -23,4 +24,10 @@ export const auth: Auth = {
2324
auth.user = null;
2425
storage.set(auth);
2526
},
27+
fetchAuthUser: async () => {
28+
const user = await api.getAuthUser();
29+
auth.user = user;
30+
auth.isAuthenticated = !!user;
31+
storage.set(auth);
32+
},
2633
};

06-tanstack-start/frontend/src/routes/__root.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export const Route = createRootRouteWithContext<RouterContext>()({
3131
},
3232
],
3333
}),
34+
beforeLoad: async ({ context }) => {
35+
if (!context.auth.isAuthenticated) {
36+
await context.auth.fetchAuthUser();
37+
}
38+
},
3439
component: RootComponent,
3540
});
3641

0 commit comments

Comments
 (0)