惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Security Latest
Security Latest
C
Cisco Blogs
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
C
Cyber Attacks, Cyber Crime and Cyber Security
NISL@THU
NISL@THU
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
D
Docker
Google Online Security Blog
Google Online Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
G
Google Developers Blog
Schneier on Security
Schneier on Security
小众软件
小众软件
爱范儿
爱范儿
GbyAI
GbyAI
J
Java Code Geeks
T
Tailwind CSS Blog
Cisco Talos Blog
Cisco Talos Blog
The Hacker News
The Hacker News
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
B
Blog RSS Feed
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Securelist
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Y
Y Combinator Blog
S
Schneier on Security
Latest news
Latest news
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
F
Fortinet All Blogs
M
MIT News - Artificial intelligence
PCI Perspectives
PCI Perspectives
V
V2EX
V2EX - 技术
V2EX - 技术
O
OpenAI News
W
WeLiveSecurity

Kent C. Dodds Blog

Implementing Hybrid Semantic + Lexical Search Simplifying Containers with Cloudflare Sandboxes Migrating to Workspaces and Nx Offloading FFmpeg with Cloudflare Building Semantic Search on my Content Helping YOU ask ME questions with AI How I used Cursor to Migrate Frameworks The Dow's Start on the Covenant Path 2025 in Review The next chapter: EpicAI.pro AI is taking your job How I increased my visibility Launching Epic Web 2023 in Review Stop Being a Junior RSC with Dan Abramov and Joe Savona Live Stream Fixing a Memory Leak in a Production Node.js App 2022 in Review My Car Accident I Migrated from a Postgres Cluster to Distributed SQLite with LiteFS I'm building EpicWeb.dev A review of my time at Remix Remix: The Yang to React's Yin How I help you build better websites The State Initializer Pattern How to React ⚛️ Get a catch block error message with TypeScript Building an awesome image loading experience How Remix makes CSS clashes predictable Introducing the new kentcdodds.com How I built a modern website in 2021 How to use React Context effectively Static vs Unit vs Integration vs E2E Testing for Frontend Apps The Testing Trophy and Testing Classifications Array reduce vs chaining vs for loop Don't Solve Problems, Eliminate Them Super Simple Start to Remix Super Simple Start to ESModules in Node.js JavaScript Pass By Value Function Parameters How to write a Constrained Identity Function (CIF) in TypeScript How to optimize your context value How to write a React Component in TypeScript TypeScript Function Syntaxes Listify a JavaScript Array Build vs Buy: Component Libraries edition Using fetch with TypeScript Wrapping React.useState with TypeScript Define function overload types with TypeScript 2020 in Review Business and Engineering alignment Hi, thanks for reaching out to me 👋 useEffect vs useLayoutEffect Super simple start to Firebase functions Super simple start to Netlify functions Super Simple Start to css variables Favor Progress Over Pride in Open Source Testing Implementation Details How getting into Open Source has been awesome for me useState lazy initialization and function updates Use ternaries rather than && in JSX Application State Management with React Use react-error-boundary to handle errors in React JavaScript to Know for React How I structure Express apps What open source project should I contribute to? When I follow TDD AHA Programming 💡 How I Record Educational Videos Should I write a test or fix a bug? Stop mocking fetch Intentional Career Building Improve test error messages of your abstractions Tracing user interactions with React Eliminate an entire category of bugs with a few simple tools Common mistakes with React Testing Library Super Simple Start to React Stop using client-side route redirects The State Reducer Pattern with React Hooks Function forms Replace axios with a simple custom fetch wrapper How to test custom React hooks React Production Performance Monitoring Should I useState or useReducer? Stop using isLoading booleans Make Your Test Fail Make your own DevTools An Argument for Automation Fix the "not wrapped in act(...)" warning Super Simple Start to ESModules in the Browser Implementing a simple state machine library in JavaScript 2010s Decade in Review Why users care about how you write code Why I avoid nesting closures Don't call a React function component Why your team needs TestingJavaScript.com Inversion of Control Understanding React's key prop How to Enable React Concurrent Mode How to add testing to an existing project Profile a React App for Performance
Why I Love Remix
2021-11-13 · via Kent C. Dodds Blog

kentcdodds.com is completely custom built by me (and team) using Remix. After writing tens of thousands of lines of code using this framework, I have developed a great appreciation for what this framework can do for me and the users of my site. I want to tell you about some of it.

In a sentence

Here's the core reason I love using Remix to build websites:

Remix enables me to build amazing user experiences and still be happy with the code I had to write to get there.

That's it. So what does that mean? Let's dig deeper...

The User Experience

There are a lot of things that impact the user's experience when they use our software. Most of the time I think people are focused on performance/speed and while that's one important aspect, it is only one part of things. The user experience includes a whole host of other aspects of your website though. Here are a few:

  • Accessibility
  • Performance
  • Content reflows
  • Reliability and availability
  • Error handling
  • Pending management
  • State management
  • Progressive enhancement
  • Resilience (behavior in poor network conditions)
  • Layout
  • Content clarity

Even the speed of development of features can impact the user's experience. So the user's experience is indirectly impacted by the maintainability of our code.

Remix helps with many of these things. Some without me having to think about it. In particular, some of the harder problems involving state management (race conditions of mutations and data loading) are completely managed within the framework. Because of this, users won't find themselves having to refresh the page because they're looking at stale data. This just happens without me thinking about it. It's just the way Remix works.

Remix does a lot to keep my website fast through its use of <link /> tags to preload assets and data at the right time. Sometimes I'm blown away by the fact that my site feels like it's static files on a CDN, but it's actually server rendered/hydrated and every page is completely unique to every user (so a shared HTTP cache on a CDN is not possible).

Remix's use of the platform APIs is what enables this. That's also what makes Remix so resilient and great for progressive enhancement as well. In poor network conditions where loading the JavaScript is slow/fails, Remix's standard API for mutations (<Form />) will actually work even before the app is hydrated. This means that the user can start getting work done with the app, even if they're on a poor connection. Much better than a completely unresponsive button whose onClick handler hasn't yet loaded (which was my own standard before Remix)!

Remix's declarative error handling means that it's easier for me to properly handle errors in the context of where the error happens. Combine this with nested routing and what you get is the ability to render a contextual error without breaking the rest of the app.

Excalidraw wireframe of a nested user interface with only one part that is broken and the rest is working

And also this works on the server as well (which is unique to Remix), so users will get the same experience whether the error happened during a client transition or a full document download.

Remix makes a terrific user experience the default. And that's one of the primary reasons I love Remix.

The Code

Apps I've helped build are used by millions of people all over the world. Building websites with Remix is the first time I can say that I'm truly happy with the code I deployed. The biggest reason for this is that before Remix I spent a lot of time just trying to deal with user experience issues. Because Remix helps so much with the user experience, I don't have so much complex code to manage myself. So all that's left for me to do is use the declarative APIs that Remix (and React) give me, to build my app and the user experience is good by default.

When I'm using Remix, I can leave my hacks at home.

Honestly that's the biggest thing I have to say about the code portion. You know how demo code is always so simple because it often skips the nuance of pending states, race conditions, error handling, accessibility, etc? Well, my code isn't quite like demo code, but Remix makes it feel pretty similar. I'm definitely still thinking about accessibility (though Remix's @reach-ui packages help a lot with that) and error/pending states. But the APIs that Remix gives me for those things are simple and declarative. I mean, here it is:

export async function loader({ request, params }: LoaderFunctionArgs) {
	// this runs on the server
	// unexpected runtime errors will trigger the ErrorBoundary to be rendered
	// expected errors (like 401s, 404s, etc) will render the CatchBoundary
	// otherwise I can return a response and that'll render the default component
	return json(data)
}

export default function AttendeesRoute({ loaderData: data }) {
	return <div>{/* render the data */}</div>
}

export function ErrorBoundary() {
	const error = useRouteError()
	// when true, this is what used to go to `CatchBoundary` in Remix v1
	if (isRouteErrorResponse(error)) {
		return <div>{/* render the error for 400-status level responses */}</div>
	}
	return <div>{/* render an "unexpected error" message */}</div>
}

Oh, and for pending states (whether mutations or regular transitions) you can stick this wherever you want the pending UI to show up (whether global or local):

const navigation = useNavigation()

const text =
	navigation.state === 'submitting'
		? 'Saving...'
		: navigation.state === 'loading'
			? 'Saved!'
			: 'Ready'

This drastically simplifies the React code that I write to the point that I don't write any HTTP-related React code. That client-server communication is completely managed by Remix and it's managed in a way that optimizes for the user's experience. Oh, and the client/server boundary can all be fully typed as well so I spend less time going back and forth between my browser and editor fixing silly mistakes.

I also love that Remix is founded on web APIs. That json function we're calling above in our loader? That's just a simple function to create a regular Response object. That's right. If you want to learn how to do something with Remix, you'll spend just as much (maybe more) time on mdn as you do on the remix docs and this brings me to another thing I love about Remix:

The better I get at building sites with Remix, the better I get at building sites for the web.

This happens naturally thanks to the fact that Remix uses so much of the web platform and defers to the web platform APIs as much as possible. (This is similar to how I feel that the better I get with React, the better I get at JavaScript.)

And because Remix is founded on the web APIs as the common interface for the server, you can deploy the same app to any platform (provided the code you bring along will run on those platforms) and all you have to do is change which adapter you're using. Whether you want to run on serverless or in a docker container, Remix has got you covered.

Remix is the jQuery of hosting platforms. It normalizes their differences so you can write once, host anywhere.

Another awesome part of the loader thing is that because it runs on the server I can hit APIs that give me far too much data and slim it down to just the part of the data I need. That means I can naturally eliminate the data overfetching problem that leads so many of us to reach for the complexity of graphql. I mean, you can still totally use graphql with Remix, but because Remix manages the client/server communication for you, you don't have to ship a huge and complex graphql client library to the browser and instead just rely on Remix to do the right thing at the right time (which it does).

And if I ever need more data than the server is sending to the client, I just scroll up in the file, change the loader to include the extra data I need, and I've got it right there. All typed and ready to go for my client-side code. It's fabulous.

I mentioned the <Form /> component earlier and how it will still work even before JavaScript loads. For that reason, it's great for the user experience. And it's also great for the developer experience, because I don't have to manage a bunch of fetch and state nonsense for my mutations. Normally, I have an onSubmit that adds an event.preventDefault(), fetch, race condition management, and cache invalidation code. Well, with Remix, all that goes away and I'm left with a declarative mutations API:

export async function action({ request }: ActionFunctionArgs) {
	// this runs on the server and I can handle the request form data here
	// whether that be a direct database interaction or calling a downstream
	// service to perform the actual mutation. It's just brilliant.
	return redirect(/* send the user wherever you like after this */)
}

export default function AttendeesRoute() {
	// look mah! No event handler or useEffect necessary!
	// race conditions handled.
	return (
		<Form method="POST">
			<div>
				<label htmlFor="name-input">Name: </label>
				<input id="name-input" name="name" />
			</div>
			<div>
				<label htmlFor="email-input">Email: </label>
				<input id="email-input" name="email" type="email" />
			</div>
			<button type="submit">Add Attendee</button>
		</Form>
	)
}

Oh, and you want validation right? Well, you're gonna want to do that on the server, so you can put it in the action there. But you may also want it on the client right? Well, you literally just move the validation logic from the action into a function and call it in both the action as well as your component. That's it. Wowza.

Conclusion

I could go on and on. There are so many blog posts and workshops inside of me. I didn't even get to talk about how simple Optimistic UI is to implement with Remix, secure authentication, abstractability (code reuse), pagination, no <Layout /> components necessary thanks to nested routing, and so much more. I'll get to all that eventually I promise.

At the end of the day, it comes back to this:

I love Remix because it enables me to build amazing user experiences and still be happy with the code I had to write to get there.

And that's just something I can get behind and push on. Care to join me?