T
TanStack9mo ago
inland-turquoise

How exactly is Tanstack Start is "client side first"

I'll be giving a talk tomorrow on Tanstack Start, and why I'm upgrading my remix app to it. One of the things I'd like to talk about that I don't know how to explain on a fundemental level but can just kind of feel when I use it for now is the whole "Client Side First" principle of it Right now the best I can do is show that certain client side libraries just work out of the box with Tanstack Start that were a massive pain to get to work in Remix... - react-hot-toast - react-tooltip - styled components I'd love to be able to explain this on a mechanical level but right now the best that I can do is... Both Remix and Tanstack Start first create your react components on the server, then hydrate them on the client, but for some reason in Tanstack Start... it just works better. I know that's not a great answer, I can demonstrate this pretty easily, but would love to back this up with a bit better of an understanding on exactly how that is achieved. If I can't get a good explanation it's not a big deal, still got tons of cool stuff to demo, but I thought it would be worth a quick ask!
74 Replies
ratty-blush
ratty-blush9mo ago
Sup Jon I subbed to your talk tomorrow I'll take a stab at a few points: - It's isomorphic (which is harder to explain than client-first)
inland-turquoise
inland-turquoiseOP9mo ago
I was told, I've been kicking it into overdrive ever since finding out
ratty-blush
ratty-blush9mo ago
So everything that runs on the client, runs on the server But not the other way So there can be server code that stays on the server (via createServerFn or use server RPCs) So that's a big departure from Next, which is very server-first So much of it is API design In Next, you have to opt-in to the client (with use client) In TSS, you have to opt-in to server-only (with cSF/use server) Remix is also very client-side first in practice However, their APIs are not designed to surface that e.g. loader is essentially a createServerFn
inc-lavender
inc-lavender9mo ago
loader is server only IIRC in remix Yeah that
inland-turquoise
inland-turquoiseOP9mo ago
I thought I was going to have to learn a new tech term with IIRC 😂
ratty-blush
ratty-blush9mo ago
Basically, TanStack Start APIs are completely optional and opt-in to the already SPA experience of the Router Remix is also "opt in", but it doesn't feel that way, because of how their APIs are designed to become "server first" (loader) as soon as you go into framework mode. Every navigation will hit the server (you can get around this easily, but that's the point, it's the default) It's very nuanced FYI, Remix and RR people don't like that I'm using "client-side first" because they are technically client-side first too It would be better to describe it as "client-side APIs that feel at home to SPAs, but without compromsing full-stack capabilities"
inland-turquoise
inland-turquoiseOP9mo ago
So is effectively all the magic starting with the fact that createServerFn can be serialized to a url and fetched from the frontend?
ratty-blush
ratty-blush9mo ago
Yeah, it's generated RPC instead of generated REST At the end of day too, I just think we're doing less weird stuff compared to Remix. Better defaults, caching, infinitely better types, and routing features that RR will never consider adding that make life so much easier The router is so much more important to compare than the full-stack stuff
inland-turquoise
inland-turquoiseOP9mo ago
All this I'm rock solid on
ratty-blush
ratty-blush9mo ago
RPC vs REST is not a big difference But it's a nice one IMO
inland-turquoise
inland-turquoiseOP9mo ago
Here's my slide that will be accompanied by alot of talking that just goes over cool features
No description
ratty-blush
ratty-blush9mo ago
Get rid of Vinxi We're moving away from it Same capabilities, just no Vinxi
inland-turquoise
inland-turquoiseOP9mo ago
Is that because of new vite stuff? v6
ratty-blush
ratty-blush9mo ago
That's part of it But mostly, just a stalled project Need more control and better cadence At the end of the day, you know better than anyone the differences
inland-turquoise
inland-turquoiseOP9mo ago
I also imagine it's a better sell to say you're not a framework built on a framework built on a framework
ratty-blush
ratty-blush9mo ago
Even if it comes down to "TSS is more intuitive and flexible for me than Remix" Yep Doesn't matter that much though Meta frameworks are just layers of abstraction
inland-turquoise
inland-turquoiseOP9mo ago
Ok, is it ok if I take a real quick attempt at making a chart to explain the "why remix doesn't feel good client side"
ratty-blush
ratty-blush9mo ago
Removing middlemen from those abstractions is a good idea always
inland-turquoise
inland-turquoiseOP9mo ago
working on something in excalidraw now
ratty-blush
ratty-blush9mo ago
Sure!
inland-turquoise
inland-turquoiseOP9mo ago
Also one quick thing, I was planning on AB'ing remix vs tanstack for a slide (also going to do this with some real code examples that I'm throwing together tonight)
fascinating-indigo
fascinating-indigo9mo ago
Will this talk be streamed? I'm interested in seeing it
inland-turquoise
inland-turquoiseOP9mo ago
And I have one big difference between the two as far as what they both offer and do well
inland-turquoise
inland-turquoiseOP9mo ago
No description
inland-turquoise
inland-turquoiseOP9mo ago
No description
inc-lavender
inc-lavender9mo ago
The way I see it the big difference is what happens after the first page load. Tanstack Start gives you the tool to have every navigation after that be client first. RR is "reload every page Server Side" by default instead Or at least Remix was
inland-turquoise
inland-turquoiseOP9mo ago
@Tanner Linsley While I had ya just wanted to make sure that you were cool with this framing. Specifically for me I remember one frustration with remix was having to write form.get and how maybe striving to make the framework feel like old school html wasn't necessarily the most ergonomic choice for complex applications
inland-turquoise
inland-turquoiseOP9mo ago
I Upgraded My Remix App... To Tanstack Start
Remix was an awesome framework, but Tanstack Starts' extremely pragmatic approach to web development has won me over!
inc-lavender
inc-lavender9mo ago
Yeah Remix's obsession with FormData just because its "pure HTTP" puts me off
inland-turquoise
inland-turquoiseOP9mo ago
works great right up until it doesn't then it's either unusable, un-agreed upon how to do things correctly, etc...
inc-lavender
inc-lavender9mo ago
The fact that it's untyped by default makes me feel all dirty If I wanted to use "pure HTTP" I'd unironically choose PHP. I've heard great things about it recently
inland-turquoise
inland-turquoiseOP9mo ago
No description
No description
inc-lavender
inc-lavender9mo ago
Tanstack doesn't have RSC yet AFAIK
inland-turquoise
inland-turquoiseOP9mo ago
The "I'm not Tanner Linsley, but did talk to him" explanation of why Tanstack feels better on the frontend is because at it's core once your frontend is loaded it's just a frontend, where remix at default is still trying to do a bunch of magic to SSR everything after that first load.
inc-lavender
inc-lavender9mo ago
And you wouldn't invalidate the entire page like Remix but just the queries you need to re invalidate if you are using Tanstack Query
inland-turquoise
inland-turquoiseOP9mo ago
I'll have the react query = TSS demo pulled up and ready to go if someone asks about that, I hope they do 🙂
inc-lavender
inc-lavender9mo ago
But maybe with actions. Tbh I don't know I never use them
ratty-blush
ratty-blush9mo ago
Yeah, forms are optional in TSS Remix loves to flex that they're progressively enhanced and that it's all "the platform" But sometimes.. the platform isn't great Or the happy path is an abstraction In this case, I would rather assume users have JS e.g. URLSearchParams sucks HTML forms aren't type safe (by default)
inland-turquoise
inland-turquoiseOP9mo ago
One of the big Code A -> Code B comparisons I want to try and get ready by tomorrow is how I made my own custom modal-rendering-through-url engine, and how Tanstack is built to fix that
ratty-blush
ratty-blush9mo ago
yeah That's a good one
inland-turquoise
inland-turquoiseOP9mo ago
But the problem is I haven't actually fixed that part yet, so I'm going to crank that out tonight
ratty-blush
ratty-blush9mo ago
And when we do parallel routes, it'll be even easier
inland-turquoise
inland-turquoiseOP9mo ago
That's one I'm excited for too
inc-lavender
inc-lavender9mo ago
Tbh I don't think these no-JS users actually exist. Crawlers for sure, but that's what SSR is there for.
inland-turquoise
inland-turquoiseOP9mo ago
Well my thinking is if they do they probably shouldn't be using remix anyways
ratty-blush
ratty-blush9mo ago
Their claim is that every user is a "No JS" user until JS is loaded
inland-turquoise
inland-turquoiseOP9mo ago
Like probably astro or something
ratty-blush
ratty-blush9mo ago
Which is fair... if you have terrible connections and massive bundles and interactive elements that get SSRd in There is a chance someone will click a button before it's ready
inc-lavender
inc-lavender9mo ago
Yeah Astro for sure if those are your constraints
ratty-blush
ratty-blush9mo ago
Yep Apps are generally behind an auth And nothing really useful can (or should be able to) happen before JS is loaded I won't say it doesn't happen or exist as an issue But it's not one that I'm concerned about And not one that I'm willing to obsess over and brand an entire framework and it's APIs around I would so much rather make anything interactive be disabled out of the box until ready than do double duty to make it work without any JS AND with JS exhausting.
inc-lavender
inc-lavender9mo ago
Sounds exhausting ye
ratty-blush
ratty-blush9mo ago
It is Nice to just skip a lot of that and just... call functions, RPCs and handle responses Do away with actions, GET/POST/PUT/PATCH/DELETE And focus on fine-grained invalidation
inland-turquoise
inland-turquoiseOP9mo ago
I have to say I'm extremely impressed with how quickly the middleware/validations feels like the best out of any react metaframework by a landslide to use
inc-lavender
inc-lavender9mo ago
The whole "submit a form" for every action is so 2004 I did that. I'm glad we don't do that anymore. AJAX was what unironically got me extremely interested in the web at the time I remember the Facebook chat the first time it came out. Magic Oh yeah let's talk middleware. IIRC the Remix team is opposed to it in principle. I'll never understand that TSS it just works out of the box and it has embedded type safety. Similarly to tRPC
inland-turquoise
inland-turquoiseOP9mo ago
Full disclosure, the only two negatives I'll be mentioning about my entire experience porting things over are: 1. Some weird error messages, the worst one being import {z} from "vinxi" 2. Some mild type conversion issues with serverFns, that are actively being worked on and corrected Both of which are extremely forgiveable in genera, not to mention that start is still in Beta.... I honestly can say I feel like I'll experience more stability on a beta product given how remix upgrades have gone in the past (plus the fact that they basically deprecated remix) Nearly everything else has been not only tolerable but legitimately pleasant to work on
inc-lavender
inc-lavender9mo ago
I've got my maybeAuth, requireAdmin, requireAuth middlewares and I use them everywhere. Just plug and play
inland-turquoise
inland-turquoiseOP9mo ago
The auth story is another thing I've been super impressed by The experience of defining something in the beforeLoad of a route, then being able to access that in nested routes is insane I think the hardest part of this presentation will be that I can't do all the things I really want to... The goal is very much to code something live because I want people to see what typesafety means not just take my word for it
inc-lavender
inc-lavender9mo ago
Coding live sounds fun 😅 The thrill of live demos
inland-turquoise
inland-turquoiseOP9mo ago
At least the framework author won't be monitoring my every mistake 🤣
inc-lavender
inc-lavender9mo ago
😅
inland-turquoise
inland-turquoiseOP9mo ago
Side note, I have a lot of experience doing this kind of thing because I was a teacher for 2 years (coding specifically)... but I've never ever done a slideshow presentation in front of actually knowledgeable devs before
inc-lavender
inc-lavender9mo ago
Anyway, it's been fun. If you have recordings please post them somewhere. I'd love to watch it. Good luck 🤞
inland-turquoise
inland-turquoiseOP9mo ago
Will do 🙂 Thank y'all so much for the help, this is a very very kind community and I deeply appreciate it
ratty-blush
ratty-blush9mo ago
Excited!!
tame-yellow
tame-yellow9mo ago
I woke up late in the night and followed your talk. Thank you @Jon Higger (He / Him) 👏
fascinating-indigo
fascinating-indigo9mo ago
Had to drop but yeah nice work. I haven't delved much into search params and stuff
broad-brown
broad-brown9mo ago
Actually a good point. You’re using a JS framework already anyway.. why pretend we’re still in 98? Yeah so happy with this counter argument. I feel most frameworks (Remix, Next) lately are optimizing for very edge case (or even invented) scenarios at the expense of general (99%) UX and DX. Glad there’s still a community with common sense Care to share? I’m about to start porting over a project and these sound useful
inc-lavender
inc-lavender9mo ago
Sure. As soon as I find the courage to get out of bed
export const maybeAuth = createMiddleware().server(async ({ next }) => {
const viewer = await getViewer();
return await next({ context: { viewer } });
});

export const requireAuthenticated = createMiddleware()
.middleware([maybeAuth])
.server(({ next, context }) => {
const viewer = context.viewer;
if (!viewer.user || !viewer.session) {
throw apiErrors.unauthorized();
}
return next({ context: { viewer } });
});

export const requireAdmin = createMiddleware()
.middleware([requireAuthenticated])
.server(({ next, context }) => {
const viewer = context.viewer;
if (!viewer.user.isAdmin) {
throw apiErrors.unauthorized();
}
return next({ context: { viewer } });
});
export const maybeAuth = createMiddleware().server(async ({ next }) => {
const viewer = await getViewer();
return await next({ context: { viewer } });
});

export const requireAuthenticated = createMiddleware()
.middleware([maybeAuth])
.server(({ next, context }) => {
const viewer = context.viewer;
if (!viewer.user || !viewer.session) {
throw apiErrors.unauthorized();
}
return next({ context: { viewer } });
});

export const requireAdmin = createMiddleware()
.middleware([requireAuthenticated])
.server(({ next, context }) => {
const viewer = context.viewer;
if (!viewer.user.isAdmin) {
throw apiErrors.unauthorized();
}
return next({ context: { viewer } });
});
If you use maybeAuth in server functions then viewer.user is possibly null. If you use one of the other two then it's guaranteed to be non null.
broad-brown
broad-brown9mo ago
Noice, smart. Thanks!
inc-lavender
inc-lavender9mo ago
I have sort of a complicated viewer object:
type BasicViewer = {
language: SupportedLanguage;
};

export type AuthenticatedViewer = BasicViewer & {
user: {
id: string;
email: string;
isAdmin: boolean;
};
session: {
id: string;
};
};

export type GuestViewer = BasicViewer & {
user: null;
session: null;
};

export type Viewer = AuthenticatedViewer | GuestViewer;
type BasicViewer = {
language: SupportedLanguage;
};

export type AuthenticatedViewer = BasicViewer & {
user: {
id: string;
email: string;
isAdmin: boolean;
};
session: {
id: string;
};
};

export type GuestViewer = BasicViewer & {
user: null;
session: null;
};

export type Viewer = AuthenticatedViewer | GuestViewer;
but you get the gist.
national-gold
national-gold8mo ago
@Jon Higger (He / Him) is it possible to still watch your talk or perhaps you can share the slides? I am interested on pitching a very similar talk within my company and it would be great to get some ideas
inland-turquoise
inland-turquoiseOP8mo ago
React Denver
YouTube
React Denver - January 7th - I Upgraded from Remix to TanStack Start
Have you heard of TanStack? If you loved the Remix framework but found some parts of it challenging to work with, you won't want to miss this talk! Jon Higger, a developer who's been there, will be sharing his insights on why TanStack Start has won him over. After a rocky experience with the Vite plugin for Remix 2, Jon found TanStack’s pragmati...
national-gold
national-gold8mo ago
thanks @Jon Higger (He / Him) great talk ❤️
inland-turquoise
inland-turquoiseOP8mo ago
I really appreciate that 🙂

Did you find this page helpful?