Skip to content

NextJS App Router - Examining the First RSC Implementation

What is the NextJS App Router and why is it important?

Why are we talking about the NextJS App Router? What’s the big deal about another application router in the React ecosystem? On the surface level, it doesn’t seem that important or interesting, but it turns out that it’s not just another run-of-the-mill routing library.

Until now, React has been a client-side library that concerned itself only with the view layer. It has avoided having opinions on just about everything that isn’t rendering your UI. But with React Server Components (RSC) on the horizon, it’s become more difficult for them to not have a concern with some of our other application layers like routing. If React is now going to have a hand in our server it’s going to need to be more integrated with our stack to be able to coordinate work from the server all the way back to the client side of the application. So what is the plan for this?

Instead of shipping a router or an entire full-stack framework, they are going to provide an API that framework authors can integrate with to support Server Components and Suspense. This is the reason why the React team has been working closely with the NextJS team. They are figuring out the API’s that React will provide and what an implementation of it will look like. Queue drumroll… Meet the NextJS App Router.

So the App Router isn’t just your grandpa’s router. It’s the reference implementation of an integration with the new RSC architecture. It’s also a LOT more than just a routing library. Glancing at the documentation page on the Next beta docs it appears to span across almost all concerns of the framework. It turns out there’s a lot of pieces involved to support the RSC puzzle.

Pieces of the App Router puzzle

On the getting started page of the NextJS beta documentation, there’s a summary list of features in the new App Router. Let’s take a look at some of the important pieces.

Routing

This seems like the most obvious one given the name “App Router”. The routing piece is extremely important to the RSC implementation. It’s still a file-based routing setup like we’re used to with NextJS with some new features like layouts, nested routing, loading states, and error handling.

This is truly where the magic happens. The docs refer to it as “server-centric” routing. The routing happens on the server which allows for server-side data fetching and fetching RSC’s. But don’t worry, our application can still use client-side navigation to give it that familiar SPA feel.

With nested routing, layouts, and partial rendering a navigation change and page render might only change a small part of the page. Loading states and error handling can be used to apply a temporary loading indicator or an error message nested in your layout to handle these different states.

Rendering

Since the App Router is RSC based, it needs to know how to render both client and server components. By default, App Router uses server components. Client components are opt-in by placing a use client directive at the top of a file. One of the main selling points of using RSCs is that you don’t have to ship JavaScript code for your RSCs in your client bundles.

You can interleave server and client components in your component tree.

Screenshot 2023-05-19 171048

Your pages and components can still be statically rendered at build time or you have the option for dynamic (server) rendering using either node or edge runtimes.

Data Fetching

One of the main selling points of RSC is being able to collocate your data-fetching with your components. Components are able to do data fetching using async/await using the fetch API.

This will probably end up being a point of controversy since according to the documentation, both React, and NextJS “extend” the built-in fetch primitive to provide request deduping and caching/revalidation.

The docs recommend that you do your data-fetching inside server components for several different reasons.

Some of the main ones being:

  • Reducing client-side waterfalls.
  • Possible direct access to databases.
  • Aggregating your data-fetching in requests to a single server call (think GraphQL resolvers).

This pattern is definitely becoming the norm in newer frameworks. These are similar benefits that you would reap when using something like data loaders in Remix. The big difference is that you will be able to do the fetching directly from your server components which is a nice win for co-location.

Caching

We touched on part of this in the Fetching section. It’s one of the reasons why NextJS is extending the fetch primitive. It’s adding support for caching your data using HTTP. If you’re used to client-side React, and tools like React Query, you can kind of think of this as the server version of that. If the data from a particular fetch request is already available in the cache, it will return right away, instead of making a trip to the origin server to get it.

The other piece of the App Router caching story has to do with server components specifically. NextJS App Router stores the result of RSC payloads in an in-memory client-side cache. If a user is navigating around your application using client-side navigation, and encounters a route segment that they have visited previously, and is available in the cache, it will be served right away. This will help to provide a more instantaneous feel to certain page transitions.

Tooling (bundler)

We still haven’t covered the entire App Router, and RSC story because in order to support RSC, you need a bundler that understands the server component graph. This is where Vercel’s new Webpack replacement Turbopack comes into play. It’s built on a modern low-level language named Rust. This provides improved build times and hot-module-reloading (HMR) times in development, which is fantastic. Since it’s a Webpack replacement, it will be able to handle a lot of different concerns like styles, static files, etc.

Goals of RSC and NextJS App Router

In this Twitter thread, Vercel CEO Guillermo Rauch highlights what he believes NextJS App Router brings to User Experience. The first one is that less JavaScript code gets shipped to the client. I don’t think anyone is arguing that this is not a good thing at this point. He also mentions fast page/route transitions that feel more like a SPA, and being able to quickly stream and render above-the-fold content like a hero section while the rest of the page below finishes loading.

I’ve heard a counter-argument from some RSC critics that aren’t as confident about these gains, and believe that RSC trades off UX for better DX (things like co-locating data fetching with components). Since RSC and NextJS App Router are still largely untested beta software, it’s really hard to say that this new, novel idea will be all that they are hyping it up to be.

There’s a major paradigm shift currently occurring in the community though, and there are a lot of new frameworks popping up that are taking different approaches to solving the problems brought on by the proliferation of large client-side JavaScript applications. I, for one, am excited to see if React can once again push some new ideas forward that will really change how we go about building our web applications.

Opening the black box

I don’t know about you, but I feel like I’ve been hearing about RSC for a long time now, and it’s really just felt like this fictional thing. It's as if nobody knows what it is or how it works. Its secrets are locked away inside these experimental builds that have been released by the React team. NextJS 13 Beta has finally started to give us a glimpse behind the curtain to see that it is a tangible thing, and what it looks like in practice. I’ll be honest, up to this point, I haven’t been interested enough to dig for answers to the half-baked questions and ideas about it swimming in my mind.

I know that I’m not the only one that has had this feeling. If you’re keen on learning more about what an RSC implementation looks like, there’s a good Tweet thread from Dan Abramov that highlights a lot of the important pieces and links to the relevant source code files.

Some other really curious people have also embarked on a journey to see if they could create an RSC implementation similar to App Router using Vite. The repo is a great reference for understanding what’s involved, and how things work.

What’s left?

Even if it does feel like a lot of things have been happening behind the scenes, to their credit, NextJS has provided a beta version of the new App Router that is still experimental, and very much a work in progress. We can try out RSC today to get a feel for what it’s like, and how they work. On top of that, the NextJS documentation includes a nice roadmap of the pieces that are completed, and things that are still in progress or not quite fleshed out.

As of the time of this writing, some of the major items on the list that look like blockers to a stable release are related to data fetching like use(fetch() and cache(). The most important one that I’m excited to see a solution for is mutations. They currently have a “temporary workaround” for mutations that basically involves re-running all of the data-loading in the component tree where the mutation happens. I think the plan is to have some sort of RPC built into the React core to handle mutations.

Final thoughts

It’s been a long time coming, but I for one am excited to see the progress and evolution of RSC through the new NextJS App Router. Since it’s still an experimental and incomplete product, I will wait before I do any real application development with it. But I will probably spend some time trying it out and getting more familiar with it before that day comes.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

Next.js 13 Server Actions cover image

Next.js 13 Server Actions

Introduction May 2023, Vercel announced that the App Router is recommended for production in the new version of Next.js v13.4, and it came with a lot of good features. In this article, we will focus on one of the best new features in the App Router: the Server Actions. Server Actions vs Server Components At first, you may think both Server Components and Server Actions are the same, but they are not. Server Components are featured in React 18 to render some components on the server side. On the other hand, Server Actions is a Next.js 13 feature that allows you to execute functions on the backend from the frontend. Back to Web 1.0 Before Single Page Applications frameworks, we were using HTML forms for most things. All you had to do was add a directory to their action attribute, and it would push the data to the server with each input name. No state was required, no async and await, etc. When Remix became open source last year, it introduced this idea again to the market. Its form actions would work even without JavaScript enabled in the browsers. It was mind-blowing. Next.js Server Actions are similar, with a wider range of use cases. They added a lot to it so you don’t only use them on HTML forms, but also on buttons and client components. How useful are Server Actions As I said in the section above, the Server Actions are like Form Actions in Remix and also they work on both Server and Client in a way similar to tRPC or Telefunc. Think about it. Instead of creating an API endpoint and making a fetch request to it from the client side, you execute a function that’s already on the server from the client like any other JavaScript function. Server Actions on the Server Side To make this post more effective, I’ll build a simple counter component with Server Actions. It runs even if JavaScript is turned off. First, create a new Next.js 13 App router project: ` npx create-next-app@latest ` Then, in the app/page.tsx, add a variable outside of the page component. `js let count = 0; export default function Home() { // ... } ` Then, add the Server Action, which is an async function with the “use server”; tag. `js let count = 0; export default function Home() { async function increment() { "use server"; count++; } return ( Count {count} Increment ) } ` After implementing the above, when you click on the increment button, you shouldn’t see the change until you refresh the page. You need to use revalidatePath to get a reactive state. `js import { revalidatePath } from "next/cache"; let count = 0; export default function Home() { async function increment() { "use server"; count++; revalidatePath("/"); } return ( Count {count} Increment ) } ` Now you have a Counter that can run with zero JavaScript. Server Actions on the Client Side You can also use the Server Action on the client component (it will be more like tRPC and Telefunc) to trigger a function on the server from the client and get data. But it will not work in the exact same way because in Next.js, each file has to be run on the server or the client. You can’t run the same file on both. So we need to move the function to its own new file, and add the ”use server”; tag on the top of the file. `js "use server"; export async function increment() { "use server"; count++; revalidatePath("/"); } ` We can import it from the client component, and use this server action there! Conclusion Server Actions are a great addition to Next.js, and it will make it easier to build full-stack applications that require a lot of work to be done in traditional frontend/backend ways like creating the API route, and then fetching it. I hope you enjoyed this article, and if you have any questions or feedback, feel free to reach out to us....

Avoiding Null and Undefined with NonNullable<T> in TypeScript cover image

Avoiding Null and Undefined with NonNullable<T> in TypeScript

Use Cases Use Case 1: Adding Two Numbers Scenario: A function that adds two numbers and returns their sum. But if one of the numbers is undefined or null, it returns the other number as-is. ` function addNumbers(a: number, b?: number | null): NonNullable { return a + (b ?? 0); } const sum1 = addNumbers(2, 3); // Returns 5 const sum2 = addNumbers(2, null); // Returns 2 const sum3 = addNumbers(2, undefined); // Returns 2 ` In this code snippet, the addNumbers()` function takes two parameters, `a` and `b`. `a` is a required parameter of type `number`, while `b` is an optional parameter of type `number` or `null`. The function uses the NonNullable&lt;T&gt; conditional type to ensure that the return value is not null or undefined. If `b` is null or undefined, the function returns the value of `a`. Otherwise, it adds `a` and `b` together and returns the sum. To handle the case where `b` is null or undefined, the code uses the nullish coalescing operator, `??`, which returns the value on its left-hand side if it is not null or undefined, and the value on its right-hand side otherwise. Use Case 2: Checking Contact Information Scenario: A class representing a person, but with optional email and phone properties. The contact()` function logs the email and phone numbers if they are defined and not null. Otherwise, it logs a message saying that no contact information is available. ` class Person { name: string; email?: string | null; phone?: string | null; constructor(name: string, email?: string | null, phone?: string | null) { this.name = name; this.email = email ?? null; this.phone = phone ?? null; } contact() { if(this.email !== undefined && this.email !== null && this.phone !== undefined && this.phone !== null) { console.log(${this.name} can be reached at ${this.email} or ${this.phone}`); } else { console.log(${this.name} has no contact information available`); } } } const john = new Person('John Doe', 'john.doe@example.com', '(123) 456-7890'); const jane = new Person('Jane Smith', null, '987-654-3210'); john.contact(); // logs 'John Doe can be reached at john.doe@example.com or (123) 456-7890' jane.contact(); // logs 'Jane Smith has no contact information available' ` Explanation: In this code snippet, the Person` class has a `name` property and optional `email` and `phone` properties. The `contact()` function checks if both `email` and `phone` are not undefined and not null before logging the details. Otherwise, it logs a message saying that no contact information is available. To initialize the properties with null, the constructor uses the nullish coalescing operator, `??`. When creating a new `Person`, you can pass null or undefined as arguments, and the class will interpret them as null. Conclusion Understanding and correctly implementing conditional types like NonNullable&lt;T&gt; in TypeScript is crucial to reduce potential code pitfalls. By reviewing examples of numerical operations and contact information handling, we've seen how this conditional type helps reinforce our code against null or undefined values. This highlights the utility of TypeScript's conditional types not only for enhancing code stability, but also for easing our coding journey. So keep experimenting and finding the best ways to deploy these tools for creating robust, secure, and efficient programs!...

Drizzle ORM: A performant and type-safe alternative to Prisma cover image

Drizzle ORM: A performant and type-safe alternative to Prisma

Introduction I’ve written an article about a similar, more well-known TypeScript ORM named Prisma in the past. While it is a fantastic library that I’ve used and have had success with personally, I noted a couple things in particular that I didn’t love about it. Specifically, how it handles relations with add-on queries and also its bulk that can slow down requests in Lambda and other similar serverless environments. Because of these reasons, I took notice of a newer player in the TypeScript ORM space named Drizzle pretty quickly. The first thing that I noticed about Drizzle and really liked is that even though they call it an ‘ORM’ it’s more of a type-safe query builder. It reminds me of a JS query builder library called ‘Knex’ that I used to use years ago. It also feels like the non-futuristic version of EdgeDB which is another technology that I’m pretty excited about, but committing to it still feels like a gamble at this stage in its development. In contrast to Prisma, Drizzle is a ‘thin TypeScript layer on top of SQL’. This by default should make it a better candidate for Lambda’s and other Serverless environments. It could also be a hard sell to Prisma regulars that are living their best life using the incredibly developer-friendly TypeScript API’s that it generates from their schema.prisma files. Fret not, despite its query-builder roots, Drizzle has some tricks up its sleeve. Let’s compare a common query example where we fetch a list of posts and all of it’s comments from the Drizzle docs: ` // Drizzle query const posts = await db.query.posts.findMany({ with: { comments: true, }, }); // Prisma query const posts = await prisma.post.findMany({ include: { comments: true, }, }); ` Sweet, it’s literally the same thing. Maybe not that hard of a sale after all. You will certainly find some differences in their APIs, but they are both well-designed and developer friendly in my opinion. The schema Similar to Prisma, you define a schema for your database in Drizzle. That’s pretty much where the similarities end. In Drizzle, you define your schema in TypeScript files. Instead of generating an API based off of this schema, Drizzle just infers the types for you, and uses them with their TypeScript API to give you all of the nice type completions and things we’re used to in TypeScript land. Here’s an example from the docs: ` import { integer, pgEnum, pgTable, serial, uniqueIndex, varchar } from 'drizzle-orm/pg-core'; // declaring enum in database export const popularityEnum = pgEnum('popularity', ['unknown', 'known', 'popular']); export const countries = pgTable('countries', { id: serial('id').primaryKey(), name: varchar('name', { length: 256 }), }, (countries) => { return { nameIndex: uniqueIndex('nameidx').on(countries.name), } }); export const cities = pgTable('cities', { id: serial('id').primaryKey(), name: varchar('name', { length: 256 }), countryId: integer('countryid').references(() => countries.id), popularity: popularityEnum('popularity'), }); ` I’ll admit, this feels a bit clunky compared to a Prisma schema definition. The trade-off for a lightweight TypeScript API to work with your database can be worth the up-front investment though. Migrations Migrations are an important piece of the puzzle when it comes to managing our applications databases. Database schemas change throughout the lifetime of an application, and the steps to accomplish these changes is a non-trivial problem. Prisma and other popular ORMs offer a CLI tool to manage and automate your migrations, and Drizzle is no different. After creating new migrations, all that is left to do is run them. Drizzle gives you the flexibility to run your migrations in any way you choose. The simplest of the bunch and the one that is recommended for development and prototyping is the drizzle-kit push command that is similar to the prisma db push command if you are familiar with it. You also have the option of running the .sql files directly or using the Drizzle API's migrate function to run them in your application code. Drizzle Kit is a companion CLI tool for managing migrations. Creating your migrations with drizzle-kit is as simple as updating your Drizzle schema. After making some changes to your schema, you run the drizzle-kit generate command and it will generate a migration in the form of a .sql file filled with the needed SQL commands to migrate your database from point a → point b. Performance When it comes to your database, performance is always an extremely important consideration. In my opinion this is the category that really sets Drizzle apart from similar competitors. SQL Focused Tools like Prisma have made sacrifices and trade-offs in their APIs in an attempt to be as database agnostic as possible. Drizzle gives itself an advantage by staying focused on similar SQL dialects. Serverless Environments Serverless environments are where you can expect the most impactful performance gains using Drizzle compared to Prisma. Prisma happens to have a lot of content that you can find on this topic specifically, but the problem stems from cold starts in certain serverless environments like AWS Lambda. With Drizzle being such a lightweight solution, the time required to load and execute a serverless function or Lambda will be much quicker than Prisma. Benchmarks You can find quite a few different open-sourced benchmarks of common database drivers and ORMs in JavaScript land. Drizzle maintains their own benchmarks on GitHub. You should always do your own due diligence when it comes to benchmarks and also consider the inputs and context. In Drizzle's own benchmarks, it’s orders of magnitudes faster when compared to Prisma or TypeORM, and it’s not far off from the performance you would achieve using the database drivers directly. This would make sense considering the API adds almost no overhead, and if you really want to achieve driver level performance, you can utilize the prepared statements API. Prepared Statements The prepared statements API in Drizzle allows you to pre-generate raw queries that get sent directly to the underlying database driver. This can have a very significant impact on performance, especially when it comes to larger, more complex queries. Prepared statements can also provide huge performance gains when used in serverless environments because they can be cached and reused. JOINs I mentioned at the beginning of this article that one of the things that bothered me about Prisma is the fact that fetching relations on queries generates additional sub queries instead of utilizing JOINs. SQL databases are relational, so using JOINs to include data from another table in your query is a core and fundamental part of how the technology is supposed to work. The Drizzle API has methods for every type of JOIN statement. Properly using JOINs instead of running a bunch of additional queries is an important way to get better performance out of your queries. This is a huge selling point of Drizzle for me personally. Other bells and whistles Drizzle Studio UIs for managing the contents of your database are all the rage these days. You’ve got Prisma Studio and EdgeDB UI to name a couple. It's no surprise that these are so popular. They provide a lot of value by letting you work with your database visually. Drizzle also offers Drizzle Studio and it’s pretty similar to Prisma Studio. Other notable features - Raw Queries - The ‘magic’ sql operator is available to write raw queries using template strings. - Transactions - Transactions are a very common and important feature in just about any database tools. It’s commonly used for seeding or if you need to write some other sort of manual migration script. - Schemas - Schemas are a feature specifically for Postgres and MySQL database dialects - Views -Views allow you to encapsulate the details of the structure of your tables, which might change as your application evolves, behind consistent interfaces. - Logging - There are some logging utilities included useful for debugging, benchmarking, and viewing generated queries. - Introspection - There are APIs for introspecting your database and tables - Zod schema generation - This feature is available in a companion package called drizzle-zod that will generate Zod schema’s based on your Drizzle tables Seeding At the time of this writing, I’m not aware of Drizzle offering any tools or specific advice on seeding your database. I assume this is because of how straightforward it is to handle this on your own. If I was building a new application I would probably provide a simple seed script in JS or TS and use a runtime like node to execute it. After that, you can easily add a command to your package.json and work it into your CI/CD setup or anything else. Conclusion Drizzle ORM is a performant and type-safe alternative to Prisma. While Prisma is a fantastic library, Drizzle offers some advantages such as a lightweight TypeScript API, a focus on SQL dialects, and the ability to use JOINs instead of generating additional sub queries. Drizzle also offers Drizzle Studio for managing the contents of your database visually, as well as other notable features such as raw queries, transactions, schemas, views, logging, introspection, and Zod schema generation. While Drizzle may require a bit more up-front investment in defining your schema, it can be worth it for the performance gains, especially in serverless environments....

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights) cover image

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights)

We take accessibility quite seriously here at This Dot because we know it's important. Still, throughout my career, I've seen many projects where accessibility was brushed aside for reasons like "our users don't really use keyboard shortcuts" or "we need to ship fast; we can add accessibility later." The truth is, that "later" often means "never." And it turns out, anyone could break their hand, like I did. I broke my dominant hand and spent four weeks in a cast, effectively rendering it useless and forcing me to work left-handed. I must thus apologize for the misleading title; this post should more accurately be dubbed "second-hand" accessibility insights. The Perspective of a Developer Firstly, it's not the end of the world. I adapted quickly to my temporary disability, which was, for the most part, a minor inconvenience. I had to type with one hand, obviously slower than my usual pace, but isn't a significant part of a software engineer's work focused on thinking? Here's what I did and learned: - I moved my mouse to the left and started using it with my left hand. I adapted quickly, but the experience wasn't as smooth as using my right hand. I could perform most tasks, but I needed to be more careful and precise. - Many actions require holding a key while pressing a mouse button (e.g., visiting links from the IDE), which is hard to do with one hand. - This led me to explore trackpad options. Apart from the Apple Magic Trackpad, choices were limited. As a Windows user (I know, sorry), that wasn't an option for me. I settled for a cheap trackpad from Amazon. A lot of tasks became easier; however, the trackpad eventually malfunctioned, sending me back to the mouse. - I don't know a lot of IDE shortcuts. I realized how much I've been relying on a mouse for my work, subconsciously refusing to learn new keyboard shortcuts (I'll be returning my senior engineer license shortly). So I learned a few new ones, which is good, I guess. - Some keyboard shortcuts are hard to press with one hand. If you find yourself in a similar situation, you may need to remap some of them. - Copilot became my best friend, saving me from a lot of slow typing, although I did have to correct and rewrite many of its suggestions. The Perspective of a User As a developer, I was able to get by and figure things out to be able to work effectively. As a user, however, I got to experience the other side of the coin and really feel the accessibility (or lack thereof) on the web. Here are a few insights I gained: - A lot of websites apparently tried_ to implement keyboard navigation, but failed miserably. For example, a big e-commerce website I tried to use to shop for the aforementioned trackpad seemed to work fine with keyboard navigation at first, but once I focused on the search field, I found myself unable to tab out from it. When you make the effort to implement keyboard navigation, please make sure it works properly and it doesn't get broken with new changes. I wholeheartedly recommend having e2e tests (e.g. with Playwright) that verify the keyboard navigation works as expected. - A few websites and web apps I tried to use were completely unusable with the keyboard and were designed to be used with a mouse only. - Some sites had elaborate keyboard navigation, with custom keyboard shortcuts for different functionality. That took some time to figure out, and I reckon it's not as intuitive as the designers thought it would be. Once a user learns the shortcuts, however, it could make their life easier, I suppose. - A lot of interactive elements are much smaller than they should be, making it hard to accurately click on them with your weaker hand. Designers, I beg you, please make your buttons bigger. I once worked on an application that had a "gloves mode" for environments where the operators would be using gloves, and I feel like maybe the size we went with for the "gloves mode" should be the standard everywhere, especially as screens get bigger and bigger. - Misclicking is easy, especially using your weaker hand. Be it a mouse click or just hitting an Enter key on accident. Kudos to all the developers who thought about this and implemented a confirmation dialog or other safety measures to prevent users from accidentally deleting or posting something. I've however encountered a few apps that didn't have any of these, and those made me a bit anxious, to be honest. If this is something you haven't thought about when developing an app, please start doing so, you might save someone a lot of trouble. Some Second-Hand Insights I was only a little bit impaired by being temporarily one-handed and it was honestly a big pain. In this post, I've focused on my anecdotal experience as a developer and a user, covering mostly keyboard navigation and mouse usage. I can only imagine how frustrating it must be for visually impaired users, or users with other disabilities, to use the web. I must confess I haven't always been treating accessibility as a priority, but I've certainly learned my lesson. I will try to make sure all the apps I work on are accessible and inclusive, and I will try to test not only the keyboard navigation, ARIA attributes, and other accessibility features, but also the overall experience of using the app with a screen reader. I hope this post will at least plant a little seed in your head that makes you think about what it feels like to be disabled and what would the experience of a disabled person be like using the app you're working on. Conclusion: The Humbling Realities of Accessibility The past few weeks have been an eye-opening journey for me into the world of accessibility, exposing its importance not just in theory but in palpable, daily experiences. My short-term impairment allowed me to peek into a life where simple tasks aren't so simple, and convenient shortcuts are a maze of complications. It has been a humbling experience, but also an illuminating one. As developers and designers, we often get caught in the rush to innovate and to ship, leaving behind essential elements that make technology inclusive and humane. While my temporary disability was an inconvenience, it's permanent for many others. A broken hand made me realize how broken our approach towards accessibility often is. The key takeaway here isn't just a list of accessibility tips; it's an earnest appeal to empathize with your end-users. "Designing for all" is not a checkbox to tick off before a product launch; it's an ongoing commitment to the understanding that everyone interacts with technology differently. When being empathetic and sincerely thinking about accessibility, you never know whose life you could be making easier. After all, disability isn't a special condition; it's a part of the human condition. And if you still think "Our users don't really use keyboard shortcuts" or "We can add accessibility later," remember that you're not just failing a compliance checklist, you're failing real people....