Skip to content

Commit b6ba80d

Browse files
committed
copy previous example
1 parent caa9c5b commit b6ba80d

56 files changed

Lines changed: 1276 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
PUBLIC_BASE_API_URL=http://localhost:3001/api
2+
PUBLIC_BASE_PICTURES_URL=http://localhost:3001
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
node_modules
2+
dist
3+
coverage
4+
.awcache
5+
test-report.*
6+
junit.xml
7+
*.log
8+
*.orig
9+
.cache
10+
.env
11+
.next
12+
.swc
13+
!.vscode
14+
.tanstack
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "es5",
4+
"endOfLine": "lf"
5+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"files.watcherExclude": {
3+
"**/routeTree.gen.ts": true
4+
},
5+
"search.exclude": {
6+
"**/routeTree.gen.ts": true
7+
},
8+
"files.readonlyInclude": {
9+
"**/routeTree.gen.ts": true
10+
}
11+
}
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
# 04 TanStack Start migration
2+
3+
In this example we are going to migrate our previous TanStack Router example to TanStack Start, which is a framework that combines Vite, TanStack Router, and other TanStack libraries for building SSR applications.
4+
5+
We will start from `03-boilerplate`.
6+
7+
# Steps to build it
8+
9+
`npm install` to install previous sample packages:
10+
11+
```bash
12+
npm install
13+
```
14+
15+
Install TanStack Start:
16+
17+
```bash
18+
npm uninstall @tanstack/router-plugin
19+
20+
npm install @tanstack/react-start --save
21+
```
22+
23+
> The `@tanstack/router-plugin` is not needed anymore as TanStack Start has built-in support for TanStack Router.
24+
>
25+
> [Quick Start Guide](https://tanstack.com/start/latest/docs/framework/react/quick-start)
26+
>
27+
> [Build from Scratch](https://tanstack.com/start/latest/docs/framework/react/build-from-scratch)
28+
29+
Update `vite.config.ts` to use TanStack Start plugin:
30+
31+
_./vite.config.ts_
32+
33+
```diff
34+
- import { tanstackRouter } from '@tanstack/router-plugin/vite';
35+
+ import { tanstackStart } from '@tanstack/react-start/plugin/vite';
36+
import react from '@vitejs/plugin-react';
37+
import { defineConfig } from 'vite';
38+
39+
export default defineConfig({
40+
plugins: [
41+
- tanstackRouter({
42+
- target: 'react',
43+
- autoCodeSplitting: true,
44+
- }),
45+
+ tanstackStart(),
46+
react(),
47+
],
48+
css: {
49+
modules: {
50+
localsConvention: 'camelCase',
51+
},
52+
},
53+
- server: {
54+
- proxy: {
55+
- '/api': 'http://localhost:3001',
56+
- },
57+
- },
58+
});
59+
```
60+
61+
Since TanStack Start handles routing for server-side and client-side, we need to move the router configuration to a dedicated file:
62+
63+
_./src/router.ts_
64+
65+
```tsx
66+
import { createRouter } from '@tanstack/react-router';
67+
import { routeTree } from './routeTree.gen';
68+
69+
export function getRouter() {
70+
return createRouter({
71+
routeTree,
72+
scrollRestoration: true,
73+
});
74+
}
75+
```
76+
77+
> `scrollRestoration` is optional, it enables automatic scroll position restoration when navigating simulating browser behavior.
78+
79+
If we run the app now, we should see it partially working:
80+
81+
```bash
82+
npm start
83+
```
84+
85+
However, we need to remove the SPA entrypoints (index.html and index.tsx) and move this configuration to the **\_\_root.tsx** file:
86+
87+
- Remove `./src/index.tsx`
88+
- Remove `./index.html`
89+
90+
And move the HTML configuration to the root route:
91+
92+
_./src/routes/\_\_root.tsx_
93+
94+
```diff
95+
import {
96+
- Outlet,
97+
createRootRoute,
98+
+ HeadContent,
99+
+ Scripts,
100+
} from '@tanstack/react-router';
101+
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
102+
import * as React from 'react';
103+
+ import normalizeCss from 'normalize.css?url';
104+
+ import materialIcons from './material-icons.css?url';
105+
106+
export const Route = createRootRoute({
107+
+ head: () => ({
108+
+ meta: [
109+
+ { charSet: 'utf-8' },
110+
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
111+
+ { title: 'Rent a car' },
112+
+ ],
113+
+ links: [
114+
+ { rel: 'icon', type: 'image/png', href: '/home-logo.png' },
115+
+ { rel: 'stylesheet', href: normalizeCss },
116+
+ { rel: 'stylesheet', href: materialIcons },
117+
+ ],
118+
+ }),
119+
- component: RootComponent,
120+
+ shellComponent: RootComponent,
121+
});
122+
123+
- function RootComponent() {
124+
+ function RootComponent({ children }: { children: React.ReactNode }) {
125+
return (
126+
- <React.Fragment>
127+
- <div>Hello "__root"!</div>
128+
- <Outlet />
129+
- <TanStackRouterDevtools />
130+
- </React.Fragment>
131+
+ <html lang="en">
132+
+ <head>
133+
+ <HeadContent />
134+
+ </head>
135+
+ <body>
136+
+ <main>{children}</main>
137+
+ <TanStackRouterDevtools />
138+
+ <Scripts />
139+
+ </body>
140+
+ </html>
141+
);
142+
}
143+
144+
```
145+
146+
> [Application Root](https://tanstack.com/start/latest/docs/framework/react/build-from-scratch#the-root-of-your-application)
147+
>
148+
> [Import global CSS styles](https://tanstack.com/start/latest/docs/framework/react/guide/tailwind-integration#import-the-css-file-in-your-__roottsx-file)
149+
>
150+
> [Vite URL importing](https://vite.dev/guide/assets#explicit-url-imports)
151+
152+
As we saw we cannot resolve the car list fetching because we removed the proxy configuration from `vite.config.ts`. Let's add an environment variable to define the API URL:
153+
154+
_./.env.local_
155+
156+
```env
157+
BASE_API_URL=http://localhost:3001/api
158+
BASE_PICTURES_URL=http://localhost:3001
159+
160+
```
161+
162+
And update the route loader to use this environment variable:
163+
164+
_./src/routes/cars/index.tsx_
165+
166+
```diff
167+
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
168+
+ import { api } from '#pods/car-list';
169+
170+
- const getCarList = async () =>
171+
- await fetch('/api/cars').then((res) => res.json());
172+
173+
export const Route = createFileRoute('/cars/')({
174+
- loader: () => getCarList(),
175+
+ loader: () => api.getCarList(),
176+
component: RouteComponent,
177+
});
178+
...
179+
180+
```
181+
182+
It looks similar to the SPA version, but now we have SSR capabilities, check it by opening the browser dev tools and see the Network tab, the initial HTML response should contain the car list data.
183+
184+
We can navigate to car details and see that working in SPA mode after the initial load. So, if we come back to the car list, it fails to fetch the data again because the env variables are not available in the browser by default. We need to expose them by prefixing them with `VITE_` or with a custom prefix configured in `vite.config.ts`:
185+
186+
> It means that loaders functions run on the server and the client
187+
>
188+
> [Isomorphic by default](https://tanstack.com/start/latest/docs/framework/react/guide/execution-model#core-principle-isomorphic-by-default)
189+
190+
_./.env.local_
191+
192+
```diff
193+
- BASE_API_URL=http://localhost:3001/api
194+
+ PUBLIC_BASE_API_URL=http://localhost:3001/api
195+
- BASE_PICTURES_URL=http://localhost:3001
196+
+ PUBLIC_BASE_PICTURES_URL=http://localhost:3001
197+
198+
```
199+
200+
_./vite.config.ts_
201+
202+
```diff
203+
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
204+
import react from '@vitejs/plugin-react';
205+
import { defineConfig } from 'vite';
206+
207+
export default defineConfig({
208+
plugins: [tanstackStart(), react()],
209+
css: {
210+
modules: {
211+
localsConvention: 'camelCase',
212+
},
213+
},
214+
+ envPrefix: 'PUBLIC_',
215+
});
216+
217+
```
218+
219+
_./src/core/env.constants.ts_
220+
221+
```diff
222+
export const ENV = {
223+
BASE_API_URL:
224+
- process.env.BASE_API_URL ||
225+
+ process.env.PUBLIC_BASE_API_URL ||
226+
+ import.meta.env.PUBLIC_BASE_API_URL ||
227+
'',
228+
BASE_PICTURES_URL:
229+
- process.env.BASE_PICTURES_URL ||
230+
+ process.env.PUBLIC_BASE_PICTURES_URL ||
231+
+ import.meta.env.PUBLIC_BASE_PICTURES_URL ||
232+
'',
233+
};
234+
235+
```
236+
237+
> [Vite Env Variables](https://vite.dev/guide/env-and-mode)
238+
>
239+
> [Use server functions if we want execute code only on the server](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions)
240+
241+
Let's change the routes to apply styles and components that we already have in pods:
242+
243+
_./src/routes/cars/route.tsx_
244+
245+
```diff
246+
- import { createFileRoute, Outlet } from '@tanstack/react-router';
247+
+ import { createFileRoute, Link, Outlet } from '@tanstack/react-router';
248+
+ import classes from './route.module.css';
249+
250+
export const Route = createFileRoute('/cars')({
251+
component: RouteComponent,
252+
});
253+
254+
function RouteComponent() {
255+
return (
256+
<>
257+
- <div style={{ background: 'teal' }}>Common layout</div>
258+
+ <nav className={classes.nav}>
259+
+ <Link className={classes.link} to="/">
260+
+ <img src="/home-logo.png" alt="logo" width={32} height={23} />
261+
+ </Link>
262+
+ <h1 className={classes.title}>Rent a car</h1>
263+
+ </nav>
264+
+ <div className={classes.content}>
265+
<Outlet />
266+
+ </div>
267+
</>
268+
);
269+
}
270+
271+
```
272+
273+
> [Migrating from Nextjs Guide](https://tanstack.com/start/latest/docs/framework/react/migrate-from-next-js)
274+
275+
Update the car list page:
276+
277+
_./src/routes/cars/index.tsx_
278+
279+
```diff
280+
- import { api } from '#pods/car-list';
281+
+ import { api, CarList, mapCarListFromApiToVm } from '#pods/car-list';
282+
- import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
283+
+ import { createFileRoute } from '@tanstack/react-router';
284+
285+
export const Route = createFileRoute('/cars/')({
286+
+ head: () => ({
287+
+ meta: [{ title: 'Rent a car - Car list' }],
288+
+ }),
289+
loader: () => api.getCarList(),
290+
component: RouteComponent,
291+
});
292+
293+
function RouteComponent() {
294+
- const navigate = useNavigate();
295+
const cars = Route.useLoaderData();
296+
297+
- return (
298+
- <>
299+
- <ul>
300+
- {cars.map((car) => (
301+
- <li key={car.id}>
302+
- <Link to="/cars/$id" params={{ id: car.id }}>
303+
- {car.name}
304+
- </Link>
305+
- </li>
306+
- ))}
307+
- </ul>
308+
- <button onClick={() => navigate({ to: '/' })}>Go back to home</button>
309+
- </>
310+
- );
311+
+ return <CarList carList={mapCarListFromApiToVm(cars)} />;
312+
}
313+
314+
```
315+
316+
> [Head Management](https://tanstack.com/router/latest/docs/framework/react/guide/path-params#seo-and-canonical-urls)
317+
318+
Update the car details page:
319+
320+
_./src/routes/cars/$id.tsx_
321+
322+
```diff
323+
+ import { api, Car, mapCarFromApiToVm } from '#pods/car';
324+
import { createFileRoute } from '@tanstack/react-router';
325+
326+
export const Route = createFileRoute('/cars/$id')({
327+
+ loader: ({ params }) => api.getCar(params.id),
328+
+ head: ({ loaderData }) => ({
329+
+ meta: [{ title: `Rent a car - Car ${loaderData?.name} details` }],
330+
+ }),
331+
component: RouteComponent,
332+
});
333+
334+
function RouteComponent() {
335+
- const { id } = Route.useParams();
336+
+ const car = Route.useLoaderData();
337+
- return <div>Car id={id}</div>;
338+
+ return <Car car={mapCarFromApiToVm(car)} />;
339+
}
340+
341+
```
342+
343+
# About Basefactor + Lemoncode
344+
345+
We are an innovating team of Javascript experts, passionate about turning your ideas into robust products.
346+
347+
[Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services.
348+
349+
[Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services.
350+
351+
For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend

0 commit comments

Comments
 (0)