I really love this kind of article because, as primarily a Backend developer, I'm finding it really really hard to make a "clean" SPA without any mentorship or help from experienced Frontend Devs. I can't find any well-documented best practices or architectures and patterns that seem to be widely accepted in the frontend ecosystem like there are for backends. Next is the closest that comes to having a "right way" but I think it's way too complicated for things I would consider a JS-heavy SPA for, which are all mainly client-side apps (Like in this article - I wonder why they chose Next since they're blocking out SSR entirely). Elm is perhaps better but I think the ecosystem is lacking compared to React and Next, and it really does its own thing so I can't be sure what I'm learning applies broadly to frontends.
Every time I think of making an SPA for a side project I hit up create-react-app, look at the uncomfortably blank canvas, and go back to Rails with ERB. TBF for most purposes Rails + Turbo gets you really, really far - just look at GitHub.
I think that's my pet peeve with modern frontend tooling, they feel like walled gardens of arbitrary knowledge that only applies to their ecosystem rather than to some fundamental learning about software.
When I learned about OOP I was able to understand OOP code in a broad range of applications, languages and frameworks, but learning how react works gives me no insight into anything but react, and it's even abstracted enough from JavaScript that you could learn React without ever properly knowing how to write JavaScript (which is something I've encountered in a handful of new devs).
The trends and gimmicks have caused this “walled garden” effect. It’s made both “practical frontend engineers” and “practical frontend architecture” a rarity. If I could write this post again, I’d talk more about how most use-cases simply don’t need a “modern tech stack”.
React is basically a glorified templating engine that attempts to enable reactive mechanisms on the client. Need “reactive”? Consider Svelte as well. Don’t need it? Consider moving the render to the build and use a lighter templating engine.
I think if we're talking about concepts at the level of OOP, we'd be comparing MVU and virtual DOMs rather than React. I learned to write UIs in F# through Elmish (used by Bolero), which was inspired by (unsurprisingly) Elm. React, also being inspired by Elm, was simple enough to transition to once I was comfortable in Bolero. It was basically MVU with components that have local state and lifetime function callbacks. I ended up tripping over TypeScript/JS more often than React
At the same time, I do know what you mean about the walled garden ecosystems. It seems unfortunate that every framework I pick up, I'm learning a new set of components everyone uses for things like virtualization. On the backend it feels less common to be using a library that's only relevant to your stack, and when there's a killer library in another language, you don't need to move mountains to use it.
I believe you're right, I meant to say React's Hooks, but now I can't find a reference to that so maybe I misremembered. I wasn't interested in previous versions of React after going over the basics in the docs a few times, but after the introduction of hooks I found it more appealing.
I continue to see this idea posted in HN and Reddit programming threads and it comes across as ignorance more than anything. The reality is that nearly all new modern web applications will be written using react or vue, with a long tail choosing a more esoteric or experimental tool because it either fits the project requirements, or the team is willing to accept more risk in favour of some other benefit (often times just curiosity).
Considering you have so few language options for frontend webapp development (excluding wasm and other such options), this really isn't a surprise to have a handful of options either. I can name more python server frameworks that are used in earnest than I can name frontend frameworks.
Part of the difference for frontend frameworks is that there's so much variety possible. Nearly every server framework has the same general design and API - some kind of routing, route handler definitions, and some additional niceties like with, db connection handling, sessions etc. I can't conceive of any other design that would work better or even be sensible.
While components have become the norm in front-end, there's still plenty of room for different architecture and actual API to use and define components, and manage state. That's where the fun of writing them comes in, and where there is still likely a better react or vue lurking in the shadows or yet to be created.
I think both can be true. There are practical reasons to use React and Vue, but that practical approach is obfuscated by the zeitgeisty culture around tools that get unwisely applied to every use-case. Frontend architecture needs to be approached soberly and prudently. Not every site needs React and GraphQL.
What I intended with my comment was to point out that the idea of new frameworks being created every week is a bit of an exaggeration and that it being the reason frontend development is fragmented is just totally incorrect.
React or Vue on their own with a sprinkling of helpful utility libraries will get most projects 99% of the way there though, and often times that _is_ simpler than trying to put together your own system for server side rendered pages and template composition.
Of course, if you're making a blog, you don't need any of this, but for _web applications_, I don't think I'd reach for SSR anymore.
AngularJS, Angular, React, Vue, Svelte. That's 5 in 11 years. I could add Knockout, Backbone, Meteor and Ember. That's 9 if you're stretching it. I don't know anything about the last 4, but the first 5 ones all offer different ways to do things. I would even argue that they are more different that the "canonical implementation of the backend MVC framework" and the "Sinatra copy" that you have in every language.
I think you have it exactly backwards - the days of using string templates to assemble HTML pages was a weird and specialized era. React and modern JS tooling brings us back to traditional client/server GUI application development, just like we did in the 90s. And that's a good thing.
GUI development has its own set of concepts and broad applications. React is somewhat novel but still fits squarely in here; the idea of a GUI component is pretty enduring.
Distributed systems development has its own set of concepts and broad applications. With modern tooling, web interactions look just like traditional RPCs.
I programmed "full-stack" through the GUI fat client era, the primitive web era, and now the modern web era. I'm much happier developing with the modern tools.
and it isn't javascript, so it's essentially a template language. It's just being turned into javascript, which then goes into the vdom, which then gets diffed with the real DOM, which generates code to reconcile it with the real DOM.
> the days of using string templates to assemble HTML pages was a weird and specialized era.
I gotta say, I think React w/ JSX is far more of a weird and specialized era.
The real difference you're talking about is having the server render each user's UI versus offloading that to the user's machine. Regardless of whether it's via strings, 1s and 0s, https, rpc, etc. Browser's have become a more powerful "general purpose" renderer, so it makes more sense that they'll continue to take on more of the UI work themselves. It's still not as good as a purpose built client though, as sluggish electron apps have demonstrated. And sometimes it might still make sense to render the UI on the server (and cache it) for either / both simplicity and performance.
Occasionally you'll even do a bit of both with a client side app being rendered server side then hydrated... talk about a weird and specialized era.
While these terms are fuzzy, JSX has less in common with the template languages we used in the 2000s and more in common with interface builder languages like XUL. The experience is assembling components on a screen rather than generating HTML. Despite all the plumbing complexity, React/JSX (and Angular et al) feels fairly familiar to GUI developers from the 90s like me.
I agree with you that "client side app being rendered server side then hydrated" is pretty weird. I also don't think this is the future of the web but rather a holdover from the previous HTML-based era. It may never go away; after all, lots of content is very well suited to simple HTML.
I'm in the same boat. But I don't think there's a lack of good info, but rather there's too much info and too many variants and options. There are probably many "correct" choices in the beginning, but we naturally want the good beginning and good long term maintainability/extendability.
Rails without a JS frontend is excellent in so many cases. The trap is when you start adding a little JS here and there to make things nicer, but soon you end up wishing you had just started with a fully JS frontend.
What I'm hoping for is a configurator/wizard where you can choose your options, and then you get a fully functioning foundational system to start from. Yes we can do it all by hand, but especially when it comes to auth and some other needs, the configuration can be a bit messy (because some details are always changing).
> What I'm hoping for is a configurator/wizard where you can choose your options, and then you get a fully functioning foundational system to start from.
Not sure if Yeoman is still around but did they not attempt something similar?
Yeoman felt a little too much like a vehicle for one class of developers to foist architectural ideas on people. I'm glad it lost momentum. It would have accelerated the frontend tech churn if it took hold, not simplify or slow it down. It was also clunky to use, and created a situation where 2 or 3 people would compete to vend the canonical Yeoman config for a new frontend technology.
In a way, it's been superseded by create-react-app and it's ilk, but I think those kind of tools are fine. If you're reaching for a tool like create-*-app, then you've already made a specific choice about a number of things, and it's just helping you to bootstrap the project.
It going to happen again soon with WASM. But I'm welcoming that.
We'll probably all end up writing full stack Ruby, or something.
I know it has been attempted many times. But like the general problem of too many options, it doesn't gain enough attention and develop a critical mass.
And in the case of setup tools, they tend to all suffer from the problem of being very difficult to change once setup. The tool you use to build the customized foundation is often unable to make future modifications if you have changed any of the generated code.
But by now we know the 99% common options people want: a choice of a few databases, (probably but optional) user profiles and auth, etc. The database side is pretty much solved, but the user management is definitely not - and it's the source of so many data leaks.
Can I say an aside, it's refreshing to hear a backend developer talk about frontend as an equal partner in the picture, rather than disparaging it?
A lot of HN is quite anti-frontend, especially when it involves abstractions like React or even languages like JavaScript. It's great that backend devs are looking into our side of the fence and talking about rational pros and cons.
In fact I’d say part of the problem with the front end ecosystem has been people trying to get by with shallow knowledge. Building a SPA (as opposed to a web page) is a technical and challenging problem. It’s people who blow that off who, for example, adopt Redux without understanding if they need it and then complain about the complexity in their codebase.
There is a resurgence of interest in HTML (really, hypermedia) oriented front end libraries like Turbo, htmx (mine) and unpoly.
The a majority of web apps being built today could be created at a fraction of the complexity by using these libraries, and they put full stack development back on the table.
> (Like in this article - I wonder why they chose Next since they're blocking out SSR entirely)
They provided a bit of rationale (kind of sprinkled across the article).
For me using Next over CRA as a default comes down to three things:
- Batteries included, high utility components and defaults for things you ought to stitch together otherwise. CRA is not a drop-in replacement for Next, even if you only render client side.
- Flexibility. Your project might just look mostly static (fully rendered at build time) , or server rendered, or client side rendered. It rarely is. If you default to Next you can easily extend and configure these capabilities (and more).
- Great DX. It's a framework that respects developers with good documentation, flexibility and a "just JS/React" kind of philosophy. There is magic that you cannot easily break through as with every framework, but so far it seems they keep the magic boundary at the right place.
As someone without this new web tech background, I have same experience about react.
create-react-app has so many dependencies and takes a lot of time to scaffold a hello world. Adding some router or some popular library produces deprecation warnings and '2 moderate vulnerability' messages. Folder size will blow up over 1 GB to make trivial apps. That's insane.
The size of CRA is largely due to its primary dependencies (Webpack, Babel, Jest, and ESLint), which are normal parts of most modern web app build toolchains.
That said, there's plenty of other alternatives. For example, Vite uses a combination of ESBuild + Rollup for its build steps, and has only a relative handful of dependencies. As a result, it installs fast, and creates + starts projects even faster.
None of that is specific to React the UI library, though.
In contrast, when I tried sveltejs, the 'official' way using degit worked sewmlessly, standard dependencies were also much smaller than when I used react.
But I will give a try to Vite, thanks for recommendation!
create-react-app is incredibly bloated. My current day job inherited a CRA app, and I've spent silly amount of time getting rid of the rubbish in there. But you don't need most of it.
For a simple production-ready React setup try the following libraries:
- react (obviously)
- redux, and react-redux (which ties react and redux together), and redux-thunk (enables asynchronous redux actions).
- react-router and react-router-dom (adapts react-router for web rather than react-native)
That's 6 libraries, but it's really only 3 as the auxiliary ones under redux and react-router are tiny. If you need to support older browsers then you might also need core-js to polyfill the newer apis, but that's it.
And if you use esbuild to build then you won't need need all the complexity that webpack brings.
Hi, I'm a Redux maintainer. Note that you _should_ be using our official Redux Toolkit package to write all your Redux logic [0]. RTK is now the standard approach for writing Redux logic. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once. RTK already includes common Redux-related packages like `redux-thunk` and `reselect` as well.
We've updated the Redux core docs tutorials to teach RTK as the default [1] [2], and have examples of migrating existing Redux logic to use RTK [3].
As for the CRA aspect: I still don't really understand why some people refer to CRA as "bloated". It has all the standard pieces that have been used in the React ecosystem for years: Webpack for bundling, Babel for transpilation, Jest for testing, and ESLint for linting, and all with a very reasonable default configuration that has had thousands of hours of effort to deal with edge cases.
I'm not saying that CRA is _perfect_. The maintenance has been spotty lately, it's taken a very long time for the Webpack 5 branch to get close to release, and I'd love to see CRA adopt tools like SWC or CRA to speed up builds. But given the problems in the ecosystem before CRA, it's done exactly what it was supposed to: provided a single command to set up a new React app with good build defaults.
> It has all the standard pieces that have been used in the React ecosystem for years
Yes, that's the problem. It has ALL the pieces, whether you are using them, whether you understand them or not. For example, most projects will use either babel typescript. CRA? It has both and extra logic to switch between the two.
I feel like a better approach would be / have been an "open book" config where there is minimal starting point and it teaches you how to add the extra bits you want/need. That way it would be kept as simple as possible, and people using CRA would actually understand the tools they are using.
I'm genuinely curious - what difference does it make to you, the end user, if CRA has that extra logic internally? You won't see any of it unless you intentionally eject, and all it really is is some checks in the Webpack config file and the startup process.
And don't even think about using "npm audit fix --force", it wants to downgrade the react-scripts dependency from 4.0 to 1.1 and introduce another 50 vulnerabilities.
Not the smoothest first impression for what's supposed to be the "on rails" introduction to react development.
I found svelte ecosystem to be much lighter than react, svelte also being much smoother to write, but sadly it's still new and doesn't have the same amount of libraries that react has.
Hold out for just a bit longer as I have. The pendulum is definitely swinging back the other way with regards to front-end development. Rails has some interesting things in the works with StimuluxReflex. Basically a SSR SPA. It's a bit like the original promise of progressive enhancement before javascript ate the world. ie. everything is server-side, then "enlivened" after with JS. So if/when JS fails, you still have a working website.
Personally I prefer LiveView from the Phoenix(Elixir) framework. But my day job still uses Rails.
There are similar projects in Django and PHP (Laravel, I think)
Same spot as you. If you find any front-end "standard" that doesn't seem "way too complicated for things I would consider a JS-heavy SPA for", let us know! I think maybe it's just... unavoidably complicated. At least any present options are, which probably means it's hard to make one that's not or someone would have done it, right?
Elm's architecture is practical, and I haven't found the ecosystem lacking after having shipped tens of thousands of lines of it over the past five years. I've shipped a bunch of big React applications also, but Elm is the one I would choose every time.
There's a lot in here that seems off if not quite wrong.
Throwing errors to be caught higher up, the way they manage redirects, and the lack of discussion around managing state (which could on its own solve lots of these problems).
So much of this seems convoluted and unnecessary for a very simple goal of building a dashboard. Their use of Apollo seems to provide no advantage over a basic REST API.
There's frankly very little discussion about actual front end architecture. I'd expect a conversation on a React application architecture to focus on things like how is client side state managed? How is server state managed and kept up to date? Are the two mingled together or kept separate? How are they kept separate? How can you build reusable components? How can you avoid components bloating in size and scope etc
Front End development today - especially with React - is so easy to get running with and there are tons of better articles that actually discuss front end architecture including lots of courses on common patterns. Beyond articles, open source projects themselves often provide great architecture discussions.
Higher order components, react-query for server state, react-router for client side routing, building reusable behaviors with hooks are all good places to get started for actual architectural design
I’ve been wanting to write more about most everything you mentioned, but ended up writing about a few interesting patterns we implemented recently. I probably should’ve titled the post “A few interesting patterns we implemented recently”.
As for state management solving the problems of pageload validation (instead of our error boundary and redirect mechanisms), can you provide an example?
Totally, I think if this article were titled patterns it'd be more accurate. Architecture has a more specific meaning (at least to me).
As for using state, when you use something like react-router, all those things, path parameters, query parameters etc are stored in state.
You can build hooks that use the hooks provided by react-router to retrieve those values in a useEffect in the relevant components. The hooks you build can be responsible for validating the path/query params, coordinating route changes etc.
The larger idea is to use your state as a means of coordinating messages that any component can hook into. So you could include in each of those detail pages a redirect route component that only fires when the hook you wrote detects that a page is invalid.
To take this to another level. You could build a Higher Order Component that automatically embeds this ability (a redirect component with a custom hook) into your item detail pages so that it's automatic.
By way of clarification, I see route validation and pageload validation as potentially separate. For example, a given route parameter may govern a whole scope of pages in the app, so it makes sense to centralize that route validation logic rather than duplicate it in each page’s validation hook. In a more traditional server (like Express), we can just handle requests which contain that route parameter with a particular middleware function that validates the route. However, if we’re just using Next.js’ filesystem router, we have to add validation in the component tree, in a wrapping component. You’re right that we could make a HOC, but then we would either need to apply it to each page file in that directory or just put it in the App render as a wrapping component, which is what we elected to do.
On the other hand, an individual page may need to run some specific logic to validate that particular load. That’s where the page validation hook comes in. We have a centralized permissions spec in the runtime which contains fallback rules for each page, so we can just update the “abort” callback with each route change so that the page can just bail out of the pageload and redirect the client appropriately.
That's the thing. In react, there's no difference between a route and a component (at least with react-router). So you can write logic that applies to a Route and logic that applies to a component the same way and apply it at the appropriate layer.
I don't know how Next.js' filesystem router works, but it seems to perhaps be the cause for this roundabout solution.
You can do middleware in client side routing with react-router similar to how you would with express. That's exactly what hooks are for and you can build your own to enable that functionality at the scope that's needed.
> Therefore, we've elected to render our application completely on the client. On Next.js this means that we wrap our React app in a "client-side only" component that looks like this:
> <pointless confusing newb-unfriendly code that invokes one of the most esoteric parts of the React API>
> This component only allows our React code to render inside the browser environment, minimizing Next.js' server-side render runtime.
Yep, sounds like a practical way to do what is literally the default.
So does the 4 paragraphs of clusterfuck about integration between Next and Apollo. How many brain cells did your team kill figuring that one out just to decide on client side rendering?
I've been using React and webpack for 6 years now, and interfacing with Rest APIs dependency-free using the standard library for at least as long as fetch has been around. All the commenters here are musing about how back end is so solved and front-end is a mess then lauding OP for posting about his "practical" stack at the same time?
This stuff is literally the entire reason front end sucks. Front end has been solved for ages with a couple of simple dependencies and all anyone wants to do is write blogs about over overengineered and/or VC funded fadtech because they're terrified of having to write a few lines of simple code.
It's not normal to just randomly spend a year "upgrading" to stuff that's barely been popular longer than that. I've been on projects that do just that, then on the next project we'll build a much bigger app from scratch in 3 months with 1/3rd of the team size.
Agreed. Having worked extensively with GraphQL, I’ve come to see it as a MASSIVE source of unnecessary complexity in almost all cases. Haven’t worked with Next.js, but using an SSR framework, then not using SSR, seems like a tonne of unnecessary complexity too. They even say:
> At Liferay Cloud, our project lives behind authentication and our clients are enterprise-level, with relatively modern browsers and powerful networks. So, SEO and client-side limitations are not concerns for us. Therefore, we've elected to render our application completely on the client.
So, why are you using an SSR framework then?
The FE stack at my current co. is React, MobX and TypeScript, powered by REST APIs that are well documented via OAS. It’s a very nice, easy to learn and maintain stack.
GraphQL's biggest problem is that it dispenses with the concept of Interface Segregation (from SOLID). Instead of having more, special-purpose, API interfaces (which RESTful APIs give us) we bring all the problems that SQL introduced for interfacing with RDBMSs in the 1970s (that have been plaguing us for 50 years now) into the realm of front-end application development.
GraphQL is popular because it helps rapidly develop backend services. BE devs don't have to spend as much time wiring up end-points and serializers. But it just shifts the problem from the BE, where it belongs, to the FE where it doesn't. Like you said, it makes FE development needlessly more complicated. Now, instead of focusing on view-related concerns, which is the point of your front-end, devs need to understand more details about the BE's domain model (unnecessarily) and need to use a text-based query language to communicate with the services. It is pure stupidity.
Great question; I should’ve mentioned this more concretely in the post.
I would’ve nuked Next completely, but we like the filesystem-based server and the ability to add imperative server-side logic cohesive with each page (getServerSideProps). In the past, this logic ends up on a separate server file and it’s less cohesive. Next keeps things a bit more organized for us.
> the ability to add imperative server-side logic cohesive with each page (getServerSideProps)
Our use case is an administrative web-app for managing a transportation network. i.e. you’ve got a bunch of cars and buses driving people around, you can use the admin web-app to see a live map of all the vehicles, see their itineraries, book driver shifts, configure rules about fares, where people can get picked up and dropped off, etc. It’s a mix of highly live/interactive content, and more static content.
Totally. With Next, your page component can export a getServerSideProps function which is used by the Next server to fetch any props necessary for the page component server-side on-demand. There’s a variation of this function for static builds called getStaticProps, which runs at build time to fetch data for props. So, even if we don’t render anything server-side, we can still run logic/fetches/etc. on the server side when a particular page is requested. This could be useful for dealing with sensitive data, for example.
Sounds like a cool app. I definitely see plenty of opportunities for static content there, like you said, but proper state management is vital for the real-time stuff.
Not the user you were asking, but the way we do it is that the OpenAPI/Swagger spec is auto-generated from server-side code (a C# .NET app with the ABP framework) during the CI build, and that is then used to auto-generate an API client (via swagger-typescript-api) which is what the frontend app uses.
When the backend team makes some changes, they notify the front-end team which then runs the auto-generation script, fixes the typescript errors (if there were breaking changes), and that's it. Pretty nice setup, IMO.
We edit the OAS with Stoplight Studio, and enforce it with server-side middleware, which validates that requests and responses conform to the OAS (400s if requests don’t conform, 500s if responses don’t conform).
At a previous workplace I’ve used OpenAPI Generator to generate clients and server stubs from the OAS, which is a nice approach to ensuring you’re confirming to the spec, too.
No, it’s never come up. This is touted as a selling point of GraphQL, but IMO is mostly solving a problem that few people have. Having worked a lot both consuming and producing external APIs, people seem happiest with idiomatic, well documented RESTful APIs, they don’t want to learn some complex RPC system like GraphQL just to use your API.
I think these GQL benefits are somewhat legit:
1. Fetch in one round trip what would take multiple with REST
2. Fetch only the response fields you need
But honestly, 1 generally saves 0-1 round trips, and for 2, while response payloads are smaller, GQL requests payloads tend to be huge, so it’s largely a wash. At least in my GQL usage, they’ve been minor wins, not worth the complexity that GQL adds.
I probably should’ve clarified that this stack is geared toward our enterprise-facing cloud dashboard. There’s a lot of data flying around and it needs to be displayed real-time. But most websites really don’t need this stuff. Front end needs to learn what backend has figured out: the stack has to fit the use-case.
I probably should’ve titled this post better, since I’ve been wanting to write more about how to make appropriate front end tooling decisions in general... not just our extremely dynamic use-case. The entire web seems to be running bloated React apps despite most sites’ needs being solved by the old stuff or new things like 11ty.
Just to clarify as well (since I didn’t in the post), we’re still using Next despite nuking the SSR because the filesystem-based server is useful to us and we like the patterns they provide for running server-side code.
I’m hoping to write a more general post soon about selecting tooling per-use-case, which certainly should not result in this overpowered and complex stack in most cases.
Do you know any good repos I could look at to see how actually practical front-end applications look like? Of course, keeping in mind it does all you'd expect a typical web app to do like CRUD, etc. I feel like I'm deep in the rabbit hole of Next.js + Apollo GraphQL but am interested to simplify whenever possible.
I've been doing web dev for almost 10 years and I've been extremely impressed with Next recently; I think it's the most "practical" framework out there right now assuming you're not wanting to go 100% client-side or 100% static-rendered (you can do each of those on their own very easily without it). The way it lets you mix both, as well as server-rendered where needed, is a game-changer.
I'm more ambivalent on Apollo/GraphQL, but I know some people have been happy with the decision to use it
We use Apollo at my new company and I have mixed feelings on GQL so far, but I asked HN about it and lots of people had positive anecdotes:
https://news.ycombinator.com/item?id=28488259
Specifically, are you referring to JWT with Apollo client on the server-side?
On the client-side, you can just use a link and get the cookie into an auth header that way. On the server-side, you need some way of getting that auth token from the cookie and passing it into the server-side runtime so that you can insert it into the auth header via a link in each server-side Apollo client instance.
Got a big chuckle out of them describing server-side rendering as this new-fangled unproven technical risk of a technology :) I get the context, but it was still funny. Ah, back to editing index.php I guess.
Sorry to be the “actually…” guy here, but actually… what the article describes is ISOMORPHIC server side rendering, which is very different from traditional SSR + client side dynamic behaviors.
The fact that the same code can render the initial static page AND the dynamic client interactive UI is the point here.
Me too. This regularly reminds me that some folks don’t have full stack experience and are coming from a completely different background. I am subsequently much more deliberate in describing architecture these days to front end devs.
It’s certainly nothing new, but the “buzz factor” impresses people who don’t know what it’s for and they implement it everywhere thinking it’s an improvement. The trend/gimmick culture in frontend is a real thing.
I like these sorts of articles. Where people describe their stack/architecture, how everything fits together, how they're using it to face their challenges, and what are the challenging parts of their setup. I feel like they can often condense many insights in a relatively short piece. I would even love to see a more in-depth version of this, describing more use-cases and how they're being dealt with, or more pain points in the setup.
Edit: looks like my comment is oddly similar to @d3nj4l's. Nice to see I'm not the only one!
I get the appealing of SSR with frameworks like Next.js (e.g., you get to develop frontend components using React, which is nice). So, my question, for the ones who know, would be: if in the following years a new generic (backend) programming language emerges that allows building frontend components in the backend (think of PHP + Html, but better; or think of Golang templates but better) as easy as it is to build components using React, then that would mean we wouldn't need anymore a myriad of different technologies: a frontend library (React), a frontend framework (Next.js) a middleware (Apollo), and a backend api (let's say Python/Go/PHP/whatever). We would only need one technology: the new generic (backend) programming language in which you can build frontend components and write business logic as well. We would be back to the 90s (think PHP + Html, but better).
The eventual linchpin is roundtrip latency. If you want to have a frontend with native level responsiveness, you simply can not move all logic to the sever. Physics will get in your way.
So any universal solution will have to include strong client side prediction in addition to handling the server (something the game industry has understood and implemented for years, and I remain confused as to why it has gained near 0 traction on the web).
To my knowledge Meteor is still the only frameworks to explore down this path, but it has never really caught on. Does anyone know of any frameworks that acknowledge this issue and manage both server and client side accordingly?
The game industry and the web industry are so closely tied together in terms of their core working model and architecture. It really is strange that there hasn't been much in the way of cross-domain inspiration or knowledge sharing.
Relay has very good support for optimistic updates on the client.
When developing an app recently I added an artificial 1s delay to server responses (during development), but because of optimistic updates you would never notice it was there.
To expand on that, CSR doesn't automagically save you from round-trips. It enables you to be agnostic where your data structure is built, but there are a lot of cases where you need to do that on the server anyway.
Then there is caching, if your stack is well-integrated and aware of mutations you can respond with 304, or you can intercept requests with serviceWorkers, or edge/cloud workers/functions etc. AKA speed of light or practically instant responses.
HN doesn't talk about it often but PHP is still the workhorse of the web. And that is for good reason. It's fast, stateless, easy to use and not just batteries included, you also get the charger, the repair kit, repair parts and adapters for seemingly every use case.
That said, it's clunky in many ways and (very) slowly losing it's dominance. It's community clings on class based, mutable OO (while even languages like Java expand in other directions). And there are thousands of little gotchas, weirdness-es and limitations that make using the language un-fun and sometimes unproductive/limiting. For newcomers it is a nightmare to fall into all these traps for the first time.
A true PHP competitor however would need to be incredibly accessible and re-imagine a lot of things. I think it would need to fully embrace modern HTTP, HTML, CSS and browser APIs. Deno is seems to partially lean on that direction for example, but that is "just" the server side so it doesn't count. Isomorphic development is a big deal. There is Imba which is also an interesting attempt. Modern languages like Clojure and Kotlin provide the capabilities but not the development accessibility and defaults (personally a huge Clojure fan though).
Haven't seen those numbers yet thank you. I was basing the above more on tiobe index, google trends and gauging the frontier of web development, which includes tooling, modern libraries.
Much of the effort in the latter part, innovation, products, tooling and marketing at the web frontier seems to more strongly focus on JS/TS/Node/V8/serverless/WASM. When I started with web dev, PHP was the de-facto standard language for small web-shops (Ruby was on the rise, Node on the horizon). I don't think that is true for the next generation of web developers.
There have been more constructs that are leaner and less traditional class based OO since a while, such as lamdas, records, more expressions, higher order functions.
LiveView is, unfortunately, somewhat of a footgun. The idea is super neat and works great on localhost -- but then somebody clicks a dropdown with a dynamically rendered list of whatever in production and has to wait half a second for a piece of UI to appear, and that's really bad.
The Nitrogen Framework for Erlang [0] is another in a similar vein (and pre-dates LiveViews).
Another is lamdera [1] which I came across on the Elm Radio podcast [2]. I've never used it, but the focus on minimising accidental complexity between front and back end was appealing, (along with the use of Elm).
Otherwise, what you are describing isn't going to be "Just Go" or "Just the language"... you'll have again a ton of libraries/frameworks on top of it... the complexity is going to be there... one way or another.
I had great experience with Lona framework [0], for smaller projects; You do everything in Python, and have a representation of your UI as a set of python objects. Then changes in python objects are propagated back to web.
To some extent, you can do what you’re talking about with Rails using ViewComponent and Stimulus. ViewComponent allows you to write reusable components in Rails. I’m using it in a project and it’s nice, but not perfect.
Well, you only need to use Node now if you want a single language (+ optionally Next, which runs on node/express and will make your UI development much smoother if it's anything beyond super basic).
Hey all, as someone ignorant to this whole SSR/Next.js stuff: I understand that Next.js is particularly popular for creating landing pages that require strong SEO. What I don't understand is, in what way is it better than say, using something like HUGO or Jekyll? If you require some dynamic content, wouldn't it be easier (and faster to build) to use a SSG and ship the JS along with all the other static assets? What am I missing here? Thanks.
Both hugo and jekyll use some kind of templating engine, and as far as templating engines go, react is better in a lot of cases. Especially when you can leverage its ecosystem of libraries, or the components for react you already have.
That gur feeling like you are missing something important is nothing more than psuedo FOMO. Its complicated and contrived for no good reason at all. SSG are good. Plain HTML is even better.
I would always advocate for plain HTML, or some basic templating engine. But such a statement highly depends on your needs. Plain HTML is great when you know exactly what you're building. As soon as you're going for something a bit more dynamic, or when certain elements require conditions, you quickly move to either SSG or some for of SSR.
Not saying you have to use whatever latest technology people are advocating, but use whatever makes sense to use. Plain HTML has its limitations, that could be solved with SSG or SSR.
Been interviewing for a few roles lately and finding the new “react dev” eerily similar to those relying on jQuery back in the day.
I ask vanilla js and low level layout/styling questions in my interviews (not gotcha questions, but instead what I consider to be fundamental skills indicative of experience) and am consistently met with confusion.
Maybe I’m too old for this game and front end devs will never again need to know about addEventListener?
What are you trying to measure by asking a low level question about say addEventListener? The people who give the best answer will be the people who most recently used that low level api and are good at sharing their knowledge about it. A better way to measure that would be to ask them to tell a story about something then if they mention say debugging an Event Listener then such a question is fair game to understand how they deep dive a topic.
An FEEs job is 90% combine components from the design system with apis from the backend team to implement a UX from the design team (and obviously debugging, deploying, etc.). For this I only care that you know what an Event Listener is or what Flexbox is or what the Box model is I don’t really care whether you know some of the low level details off hand (unless you reference it in a story).
Maybe I am not the only person on the planet who still uses Vanilla JS and plain Node.js.
I mean I have a lot of experience with various front end component systems on desktop on the web, including Microsoft stuff and in the browser some Angular, React and Vue. But for my current project which is a little bit involved, I decided to skip React. It is actually working out fine. I assume React devs who see it will suggest that it's time to take me out behind the barn and put me out of my misery though.
I've built a few small projects with just backend rendered views in Node.js, and then did all the dynamic "SPA" like features with Unpoly. Turned out a lot better than I thought. Things such as authentication being a lot easier and not having to build an API really helped speeding up things.
I remember interviewing people for a front-end role as well, and when asked how they would decide between traditional HTML rendered on the server, or a framework like react/vue. Some flat-out couldn't even comprehend what I meant. I honestly feel like this is becoming a problem, where junior devs don't even know how to build anything without react/vue (or the likes).
Good! We need to get back to the basics and stop shoehorning React into every application.
I regret that my post is yet another React promotion. Not intended: architecture depends on use-case. I’m hoping to write more soon about selecting tools appropriately, which probably doesn’t mean React in many cases.
HTML was never gone :)
I have and still working on big e-commerce websites.
For example, Ikea for product lists sends mini chunks of simple HTML. No JS involved.
What I see is the problem that people think that "Frontend development is easier than backend development".
For me, they have different challenges and different ways of thinking. Getting a good full stack developer is kind of getting a good doctor, which has at least to specialities.
> Frontend development is easier than backend development
This always amuses me when it comes from junior front-end devs who are scared to touch the backend because they've never done it before. I always tell them not to worry: the backend is much simpler.
> What I see is the problem that people think that "Frontend development is easier than backend development".
I hate that statement so much. Even had some serious arguments with "back-end developers" who were constantly mocking HTML/CSS/JS. Turned out that when pushed to do it, they just couldn't do anything and had to ask for help.
Writing proper reusable HTML/JS isn't easy, it also requires thinking on how to structure and split up things. Especially when you throw some CSS into it.
I've been a backend developer for many years, and for the last 6 years I've been doing frontend.
I can assure you those developers mocking frontend devs can't even do backend if taken outside their big ass framework (django, rails, symfony,etc etc), because they believe everyhing is as easy as those frameworks put it to them. Somebody that had to work outside of that where you have to take many decisions and make tons of trade offs and find how to organize code and manage dependencies, etc will understand better how difficult frontend is. True we sometimes overcomplicate things more than needed (redux, rxjs, etc...) but even when we do not, this is still freakin difficult to get right.
The back-end can render that content when requested, adding very little overhead to an AJAX request just returning the raw data, and it can pre-render and/or cache objects as well. Both Facebook and Reddit do similar when loading extra content into an existing page - they embed HTML content in the JSON response for things like profile popovers or when loading more comments. Saves the front-end from having to render a template (takes time to load the template beforehand and time to render it, maybe for many items) or build it programmatically. Directly using AJHX to get HTML is the same when there's just a chunk of HTML with no metadata.
In my experience PJAX/turbolinks + server rendering will get you 80-100% of the way depending on use case. The remaining pieces can be filled in with careful and disciplined use of something like React/Preact or Stimulus.
When an article starts by listing a bunch of tools using phrases like "easy", "simple" and "commonly used" as explanations why the tools were chosen, it's not an article about architecture.
Personally, I wish front-end frameworks were literally that, a set of files you can toss in a directory and serve (with nginx, apache, or any static host like netlify), with your backend as a separate deployable bundle.
SSR, progressive enhancement and all these tricks to speed up load time aren't really necessary if you just keep complexity down and your bundle size small.
Great question. In fact, that question has more to do with “architecture” than most of my post.
The primary reason we went with Apollo is that it’s flexibility-minded and well-documented, making it easier for new engineers to work with. If everything was ideal, we’d use Relay. Relay’s patterns are better (IMO) but it can be confusing for new folks to use it idiomatically. So, it’s primarily a dev-experience consideration for us.
I think this article shows how far we still have to go. Things like SSR with Apollo (or some other data layer) should be a solved problem by now, and not require every developer to ask themselves these questions again and again. Thankfully I think the next.js team has been doing really great work in that area so I’m hopeful for the next couple years.
> should be a solved problem by now, and not require every developer to ask themselves these questions again and again
This applies to so very much of web developement. We have spent so much collective time building web apps, and some have also put a lot of time into building libraries and frameworks, that with all the brainpower and effort, things should be easier now than they are.
I believe this is because we've had too much freedom. Not suggesting we should have stuck with C or Java, but it seems that many people who are motivated and intelligent have decided to build their own better mousetrap. So now we have 1,000 mousetraps. Or maybe 10,000. If we could somehow redirect that effort into 3-4 primary options, I think we'd be overall better off.
Take Wordpress for example. You can do just about anything with Wordpress and some custom plugins. It's a miserable experience mostly, but it's possible. Imagine if some of the smartest people behind some of the best frameworks and libs collectively worked on one new system (plugin-based). As long as there were a good plugin interface, the users (devs) could then use whatever language they prefer to provide business logic and customization.
One of the benefits of Wordpress is that it runs on PHP. It's very easy to find a cheap web host and just copy the files over. Many web hosts even have a simple button that sets it up for you.
A viable alternative would need to be just as simple, and currently that implies it should probably be implemented in PHP, which will exclude developers who want experience with "blog-friendly" languages.
PHP, _like Javascript_, can be great when used with some extra care. I have no beef with PHP (although omg do they waste space with curly braces on their own lines and vast amounts of indentation!)
A good foundation can be in any language, such as PHP. But it needs to have a modern, better architecture with a more consistent base library/API for the plugin writers to take advantage of. WP is like 1.0 of this. We've learned so much in the last 10-15 years; it would be nice to have a completely rethought foundation.
But for the plugins, I think there should be a standard interface layer (based on probably http, tcp, pipes, files, even memory stores for communication). Then plugins could be written in any language, hosted locally or remotely, and sandboxed.
Not planning on getting dragged into a debate about whether GraphQL is a good tech choice here (even though I won't pretend i'm not a massive fanboy). But the "with-apollo" Next.js example used as a reference (and then decried as being a bad experience and unscalable), is a pretty bad representation of how it can feel to use GraphQL with Next.js.
I've never used Apollo in anger, but i've used Relay with Next.js for a few projects and it's been a dream. The biggest downside has been the size overhead of the Relay client itself (approx 40kb over the network), and the size of the serialized data (but that's the same price you'd be paying whether you were using GraphQL or not, just an unfortunate reality of the hydration model).
I’d love to hear more about your experience with that “with-apollo” pattern. I decried it without using it first, but I see the problem as a coupling/cohesion imbalance.
I've mostly used the Relay examples as a starting point, though they've generally need to be fixed up to avoid some bugs. In the olden days of getInitialProps (still available as an API, but its use isn't actively encouraged) things were easier and more elegant, getServerSideProps and getStaticProps both force things to be a little uglier.
Look at the last file (PostDetail.jsx) to see what it's like using GraphQL as part of a page (PostLayout isn't defined here, but assume it's just a regular React component using Relay's useFragment hook).
We are using every new technology because why not, but we don't think about our use cases.
Things like "/items/12345/detail" let me believe that this is not a spa really? More like a good old MPA?
I'm not sure if the chosen technologies are the right ones for the jobs.
In general, I don't understand the trend to default to something like reactjs. We have and had alternatives.
What I see is that people complain about technologies which are not build for what they are using. To get good performance and bundle size out of SPA like apps, you really need to put in work. Maybe a simple Laravel/RubyOnRails website would have been the better stack for a lot of projects which are right now written in reactjs/nextjs.
If I understand this post correct they seem to like the idea of putting routes in the file system (Next uses magical file names as a way to generalize routes for those who haven’t used it).
To me this makes absolutely no sense, a route doesn’t map to the file system in anything more than the most trivial index.html cases and you lose the ability to do things like “resource routes” to avoid mindless declaration of routes. On top of that, I would assume Next creates a route definition in memory anyways and doesn’t read from disk every request so it’s just pollution of the file system with the additional downside of making navigation unnecessarily hard.
This article is great timing. I'm working on a "next gen" FE app and it validates most my decisions.
Only real difference in my stack is I'm using redux toolkit query to connect with a Rest API instead of GraphQL (GQL would be nice, but can't have it all).
I'm a HUGE fan of antd! I've been working with it since like 2017 and it blows my mind what I can build in a few hours.
I haven't used tailwind, but wanting to look into it. Anyone have practical advise for using tailwind alongside a component library like antd?
Using Tailwind alongside anything is pretty simple. It just generates stylesheets for you based on what you use. All you need to do is set up the build (so that it knows which style rules to include and which to purge) and then start using Tailwind classes in your markup.
As for “best practices”, I’d say there’s not many problems you’ll run into using something like Tailwind. Any configuration improvements you make probably won’t break anything, but you should still read the docs before you get started.
maybe a bit off topic but i'm still on the fence if front end will ever be "solved".
The difference is that backend is always going to be some variation of CRUD, so the architecture isn't changing too much. front end being the actual user-facing layer means it will always evolve with human-computer interface (UX?).
Mobile phones completely changed the way we thought about frontend, and it's just time until the next big change in form factor comes... and a bunch of new frameworks will pop up
I’d say backend architecture is also changing. React is older than Kubernetes.
I do get your point though that UI dev probably sees more variation since you need to adapt to the form factor of the user’s device, which changes significantly every few years.
Not the OP nor the post's author. We have a similar stack (React + NextJS + TypeScript + Mobx) but use a traditional REST API using Django Rest Framework. There are some interesting topics in the post and in these comments that I figured I could expand on:
1. The primary reason we chose NextJS (with SSR) was to be able to write a declarative React component that is run on the server and the client. For example a button to follow or unfollow a page (think: like/unlike in social media). In the previous generation of our application, we wrote the button in PHP that rendered HTML and toggled UI state on the front end using JS/jQuery. That meant writing the button logic (and HTML/CSS) in two places instead of one. Now, we have a single React component that renders itself based on state; it's the same component on the server and client.
2. I don't understand the extra work to avoid SSR. SSR is great and can be used behind authentication--there's no actual need to work around it. Additionally, in a NextJS application, SSR is only used on the first page load. When you click from page to page, it's all client-side transitions.
3. @yashap said "The FE stack at my current co. is React, MobX and TypeScript, powered by REST APIs that are well documented via OAS. It’s a very nice, easy to learn and maintain stack." Agreed. Same here. It's wonderful. In particular Mobx. A lot of the comments seem to say "I expected the article to talk about how to maintain state in a React application"--that's how. Use Mobx. No matter what reactive JS framework you're using, use Mobx to maintain "global" state.
4. The discussion of "why move to NextJS when my Webpack config from 6 years ago works just fine" is a good one. Two big reasons we moved to NextJS (away from a Webpack config from 7 years ago) were: the handoff from server -> client is more seamless and the developer experience with hot module reload for server + client sides was worth it. Any JS application these days is going to be a tooling nightmare: Webpack, Babel, TypeScript, ESlint, React, polyfills, plugins, and then dependencies. That's awful no matter what JS framework you choose. Create React App doesn't fix that, it just hides it for the first step of creating a react application. You still have to maintain all that nonsense.
> Any JS application these days is going to be a tooling nightmare: Webpack, Babel, TypeScript, ESlint, React, polyfills, plugins, and then dependencies. That's awful no matter what JS framework you choose. Create React App doesn't fix that, it just hides it for the first step of creating a react application. You still have to maintain all that nonsense.
Doesn't NextJS do the same? The alternative to compare to would be a plain setup without any heavyweight boilerplate. These days you could you esbuild, but a webpack config can be ~50 lines and quite simple if you build it up from scratch.
Next.js is rewriting its core with a Rust-based compiler. SWC (JavaScript and TypeScript compiler written in Rust) will replace two toolchains used in Next.js: Babel for individual files and Terser for minifying output bundles.
Not the original commenter but would appreciate more on this. If you can be bothered, could you also expand a bit on how Next.js works under the hood when it comes to forcing CSR à la
{typeof window === 'undefined' ? null : children
On a related note, thanks for what you do for Next in the discord.
Read @leerob's answer first. Additionally, the notion of "forcing" CSR might be a misnomer. The way I understand it, NextJS always prefers CSR; the server renders HTML on first page load and the NextJS framework attaches JS stuff to the page on render to handle the client-side interactivity. So, if you turn off JS on your browser and load the page, everything works out of the box (page renders HTML, links work, etc). There are ways to specify server-only data requirements.
Once the client-side JS has taken over, the `next/link` component [1] is used to render and listen to events when a user clicks on a link. That component tells `next/router` to render the page that was clicked on. All of this happens on the client by default. If JS is disabled on the client, then the HTML rendered by the `next/link` component on the server is a simple `<a/>` tag and a normal browser page load occurs.
The easiest way to force CSR would be to render nothing on the server, `useEffect` and then render everything on the client. This could be at the top level of your application. But, I'm guessing most folks don't actually want that. It's typically a better user experience to serve some loading state/skeleton pre-rendered from the server, followed by loading data client-side (instead of full CSR). If you have NPM packages or code that can only execute on the client-side, you can also use next/dynamic to load specific components in client-side only.
Yes, NextJS does the same. After re-reading my comment, I don't think I was clear: our reasons for moving to NextJS were the two mentioned, not because it was less complicated than our Webpack config from 7 years ago. It's not less complicated, it's different. NextJS does some weird things under the hood to make it seamless (like CRA does) but it can make things even more complicated to go with nonstandard Webpack rules or Babel presets/plugins. Overall, I think any JS application written in this day and age is going to be a tooling nightmare--NextJS included. In our case, it's not bad enough to force us away from JS (or TypeScript) yet.
I haven't spent much time with Next.js, but luckily React will soon natively support[0] SSR. Wonder if Next.js will still be around once React Server Components are fully released.
In short, the answer is likely yes. React's server component stuff is about providing primitives to make it possible to address some of the downsides of the current approach to building apps/sites that follow the SSR-with-SPA-handover (hybrid rendering) model. There'll still be a need for more holistic frameworks that sit in front of React. SSR itself has been possible with React since 2014 (allowing for a few memory leaks), Next.js just eliminates a lot of the grunt work.
The React team is working with the Next.js team (amongst others) to make sure there's alignment.
> Likewise, don't choose server-side rendering just because it's buzzy and sounds powerful. Server-side rendering does basically the same job as client-side rendering: generating HTML.
Well not quite. With client side rendering you now have manage state in the browser as well as build API end points for each screen you want to create.
lol it would be funny if in a few years... Google(crawler) becomes good enough to read all text from images(they are probably there already and beyond)
And instead of rendering html or static site generators...we just export everything as one or a few(for responsive) jpeg :) ?
Practical for front-end devs, not so much for us users who have to live with laughably shitty js-reimplementations of decades old built-in browser functionality…
Heaven forbid you’re on a shitty internet connection, these inane js-behemoths just die from a dropped tcp connection, since they’re built on the most optimistic assumptions about network connectivity.
I remember when accessibility was a part of web development, but I guess the ROI was to low.
I did mention that our clients are enterprise-level.
But I did fail to address a more general decision-making process regarding choosing frontend architecture. If I did (and I probably will soon), I would talk a lot about the egregious initial load times on too many blogs that roll crazy dynamic tools for what should be a completely static site.
Yeah, I’m sorry. Looking back at it, that comment was written in general exasperation with the current state of the web, not really at you in particular.
Finally! You've done it! All the other front end architectures Are meant to be non-practical. Say what's special your solution in the title. Avoid using empty words. Less nose, more signal
Right?! Yeah, the title should be “A few patterns we implemented lately”. I do have more thoughts specific to practical frontend architecture though, so I guess I’ll just hope you see the next post.
Every time I think of making an SPA for a side project I hit up create-react-app, look at the uncomfortably blank canvas, and go back to Rails with ERB. TBF for most purposes Rails + Turbo gets you really, really far - just look at GitHub.