#How exactly is Tanstack Start is "client side first"

138 messages · Page 1 of 1 (latest)

silk sky
#

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!

dry axle
#

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)
silk sky
dry axle
#

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

formal harness
#

loader is server only IIRC in remix

#

Yeah that

silk sky
dry axle
#

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"

silk sky
#

So is effectively all the magic starting with the fact that createServerFn can be serialized to a url and fetched from the frontend?

dry axle
#

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

dry axle
#

RPC vs REST is not a big difference

#

But it's a nice one IMO

silk sky
#

Here's my slide that will be accompanied by alot of talking that just goes over cool features

dry axle
#

Get rid of Vinxi

#

We're moving away from it

#

Same capabilities, just no Vinxi

silk sky
#

Is that because of new vite stuff?

#

v6

dry axle
#

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

silk sky
#

I also imagine it's a better sell to say you're not a framework built on a framework built on a framework

dry axle
#

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

silk sky
#

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"

dry axle
#

Removing middlemen from those abstractions is a good idea always

silk sky
#

working on something in excalidraw now

dry axle
#

Sure!

silk sky
#

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)

lean plinth
#

Will this talk be streamed? I'm interested in seeing it

silk sky
#

And I have one big difference between the two as far as what they both offer and do well

formal harness
#

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

silk sky
# silk sky

@dry axle 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

formal harness
#

Yeah Remix's obsession with FormData just because its "pure HTTP" puts me off

silk sky
formal harness
#

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

silk sky
formal harness
silk sky
#

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.

formal harness
#

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

silk sky
#

I'll have the react query = TSS demo pulled up and ready to go if someone asks about that, I hope they do 🙂

formal harness
#

But maybe with actions. Tbh I don't know I never use them

dry axle
#

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)

silk sky
# dry axle e.g. URLSearchParams sucks

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

dry axle
#

yeah

#

That's a good one

silk sky
#

But the problem is I haven't actually fixed that part yet, so I'm going to crank that out tonight

dry axle
#

And when we do parallel routes, it'll be even easier

silk sky
#

That's one I'm excited for too

formal harness
#

Tbh I don't think these no-JS users actually exist. Crawlers for sure, but that's what SSR is there for.

silk sky
dry axle
#

Their claim is that every user is a "No JS" user until JS is loaded

silk sky
dry axle
#

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

formal harness
dry axle
#

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.

formal harness
#

Sounds exhausting ye

dry axle
#

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

silk sky
#

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

formal harness
#

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

formal harness
#

TSS it just works out of the box and it has embedded type safety. Similarly to tRPC

silk sky
#

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

formal harness
#

I've got my maybeAuth, requireAdmin, requireAuth middlewares and I use them everywhere. Just plug and play

silk sky
#

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

formal harness
#

Coding live sounds fun

#

😅

#

The thrill of live demos

silk sky
#

At least the framework author won't be monitoring my every mistake 🤣

formal harness
#

😅

silk sky
#

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

formal harness
#

Anyway, it's been fun. If you have recordings please post them somewhere. I'd love to watch it. Good luck 🤞

silk sky
#

Will do 🙂

#

Thank y'all so much for the help, this is a very very kind community and I deeply appreciate it

dry axle
#

Excited!!

strong ether
#

I woke up late in the night and followed your talk. Thank you @silk sky 👏

lean plinth
#

Had to drop but yeah nice work. I haven't delved much into search params and stuff

gloomy beacon
gloomy beacon
gloomy beacon
formal harness
#

Sure. As soon as I find the courage to get out of bed

formal harness
# gloomy beacon Care to share? I’m about to start porting over a project and these sound useful
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.

gloomy beacon
#

Noice, smart. Thanks!

formal harness
#

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;

but you get the gist.

slender wolf
#

@silk sky 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

silk sky
#

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...

▶ Play video
slender wolf
#

thanks @silk sky great talk ❤️