Skip to content

Instantly share code, notes, and snippets.

@nross83
Last active April 13, 2025 22:44
Show Gist options
  • Save nross83/40bf55595536e60bd6ea766bab07e7c9 to your computer and use it in GitHub Desktop.
Save nross83/40bf55595536e60bd6ea766bab07e7c9 to your computer and use it in GitHub Desktop.
Feedback

Use Vite

It has a built-in dev server and with a single config file you can have really fast local builds with hot module reloading and also production builds. It will also let you do any of the proxy server custom stuff you might need as well. It's the gold standard for any React SPA application right now.

Use Eslint and Prettier

Prettier is a game changer for me. Everyone on our team writes Javascript however they want (semi-colons or not, tabs vs spaces, curly braces on same line or next, etc) and Prettier normalizes it to look like a single person wrote it all.

For Eslint, I use the eslint:recommended set of rules and if I don't like something I will override it in the config. Prettier also has a plugin for eslint which ignores all linter rules related to formatting (since Prettier does the formatting automatically). If you decide to use Prettier, I would suggest that as well.

To enforce usage of Prettier and Eslint, I use lint-staged. It allows me to run scripts whenever anyone makes a git commit to my project. I use it to run prettier and eslint --fix and it'll do all the formatting and everything you want on pre-commit. You can even use it to run unit tests or things like that if you'd like as well. Huge for maintaining consistency, catching small bugs, and completely eliminating all the formatting nitpicks reviewers might have.

CSS

I used to use a big CSS styles file and compose chunks of my app's styles with it. Now I use React components as my composition layer and scope my styles to them locally.

  • For example, instead of having BEM-style css classes (e.g. product-component-card), I instead create a ProductCard component and use that everywhere instead. The component is what I've found composes better than CSS rules I reuse through my app. Even dumb little things like using a <Header level={3} /> instead of <h3 /> and it has all the styles baked in.
  • Instead of using BEM-style CSS class naming conventions (which can lead to conflicts if naming isn't done properly), I instead use CSS Modules. I only ever use a single css file with a single JS file. CSS Modules ensures all classes are globally unique so I can scope class names to a single file and name it generic things and it'll work. My stuff typically looks like this:
// MyComponent.tsx
import styles from './MyComponent.module.css';

const MyComponent = ({ title }) => {
  return (
    <div className={styles.container}>
      <input className={styles.input} />
      <div className={styles.footer}>
        {title}
      </div>
    </div>
  );
}
/* MyComponent.module.css */
.container { }
.input { }
.footer { }

/* 
 * In my HTML, when I look at the markup, the classNames will look like this: 
 * .MyComponent-container-ab3817a
 * 
 * This makes it really easy for me to find which file/component has the style I need to edit from
 * the markup in Chrome dev tools when I need to.
 */

I see you also have Emotion installed. I haven't used it personally, but know it's pretty widely used. Another option is Tailwind. I've heard it's a mental-shift to use it, but once you do it for a week devs say they never want to go back. When I got to my company, I migrated our app from using BEM-style global CSS to CSS Modules because it was easier, but if I started the project from scratch I would've used Tailwind. It's been taking the front end world by storm. If you decide to use Vite, it handles CSS Modules out of the box (just name your file extension .module.css and import it like my example shows and it works).

Use TanStack Form

I can't emphasize this one enough. I've had homegrown form libraries on previous teams because React was new and there wasn't anything out there. It was okay, but we would always get new requirements that I would have to build into it -- the API got messy and it got to the point where nobody on the team ever wanted to touch it. Now I use a Form library and will never again try to write my own. The best ones out there come loaded with excellent documentation and cover everything I need and more. The best one I've seen is TanStack Form (though I haven't actually used it because we use Ant Design form which I don't recommend). This one has first-class Typescript support all the way through in a way no other library has managed to do before. Tanner Linsley, creator of the TanStack open source projects, is one of the best out there in React open source. I'd use his Form lib and Table components without reservation.

Another decent library is react-hook-form if TanStack Form doesn't work out for your needs.

Formerly known as React Query. This library is incredible and makes working with async data fetching in React SPAs second-nature. It has been around for many years and is battle tested. It also comes with really cool data caching in memory by default (though you can tune that to your heart's content). I would recommend ditching any use of fetch in favor of this. It's become a standard in any React SPA I've used. All of my queries are hooks now. You can use it for any type of queries or mutations.

Use Playwright for e2e testing

Devs are mixed on e2e testing. Most of them that hate it used Selenium and Selenium sucks. I have my scars from it. Playwright is incredible. If you can spend a day playing around with it and just instrumenting something really simple, I'd highly recommend it. They have a record capability that allow you to click and type around your site and it writes the test for you (codegen). If you do this, I'd recommend not using CSS class names to target your elements you want to assert on. Instead, I'd recommend using user-visible behaviors (More on that here)

I typically only test the happy paths with Playwright and don't both with edge cases, errors, or validation tests. I leave those for integration tests.

Use vitest for unit and integration testing

Jest used to be the library of choice for unit testing, but with Vite taking over the world of SPA, people have also been moving to their testing library, vitest.

For unit tests, I typically don't do a lot of them in frontend code, but there are always a couple functions that scare me whenever I need to touch them later because they get really complex. For those, I take a few minutes and write a couple unit tests. Having an AI editor like Cursor or a VS Code plugin like Github copilot makes this stuff simpler than ever. I'd recommend not writing unit tests for small components. I'll write them for functions only and then write integration tests on full "page" components instead.

For integration tests, I use vitest, but also use a library called React Testing Library. I use it to mount a page programmatically and I mock server responses with MSW. I'll use MSW to fake responses server responses and I'll handle things like testing 404s or 500s error display on the front-end. I'll also do some edge case testing with this too. I don't do it for every page -- simple ones don't need it so much, but a complex page can benefit.

For example, I have a really complex Table component I made which syncs filter choices the user makes with the URL and vice-versa. It's complex stuff and there are a bunch of different code paths and exceptions. I know I won't do a good job testing all the edge cases if I make changes to that code so I wrote a bunch of integration tests for it. Now I make changes and if it passes, I move on. I don't need to spend the extra 5 minutes running through manual testing and likely forgetting to test some weird cases. I'm convinced those tests alone saved me hours of work and several bugs.

I don't suggest test coverage personally. Some people go nuts over 80% code coverage with their tests. I think that more often than not is a waste of time and effort. I'll only test things that I think will save me more time in the long run due to hotfixes or if I think it's likely I'll cause regressions because testing it thoroughly is hard.

Why not playwright instead of integration tests? My playwright e2e suite runs in about 8 minutes. My integration tests run in about 30 seconds and those tests test a lot more. Use e2e for a sanity check on how your entire product is performing. Use integration when you have pages or really complex components you want to test. Unit tests for me are rare and typically just test utility functions. I'd say my testing mix is probably 40% e2e, 50% integration, and maybe 10% unit (if that).

Use GitHub actions for CI/CD

Instrumenting scripts to run when a PR is created or merged is really easy with GitHub actions. They are pretty cheap and you can write some simple yaml instructions and have them call scripts you already have in your package.json. I use the following in all my repos:

  1. Deployment job. Trigger: on push to develop branch. Job: run vite build, extract localization strings, and copy bundles to CloudFront or AWS S3. I do a similar thing for my main branch, except any merge to that branch kicks off a push to production environment instead of dev.
  2. CI job. Trigger: on create pull_request to develop branch. Job: run eslint, typescript type checking, vitest, and playwright.
  3. Full E2E Suite. Trigger: a regular cron schedule twice a day. Job: run my full playwright test suite. I run a subset of them in #2, but this one runs all of them. They take 10-15 mins to run, so I wanted to separate them out.

I'veto the point where humans don't run tests or deploy manually anymore (unless fixing something locally). Everything runs in automated fashion in GitHub actions. If things pass the PR tests, I am pretty confident they won't cause a regression. Deployments just happen automatically whenever I merge a PR to the develop or main branches. Less human error and happens quickly.

Need component library?

I would highly suggest using a good component library. Accessibility, keyboard navigation, etc are really, really hard to get right. There are two approaches I've seen for how people do this:

  1. shadcn/ui: Gives you fully accessible, solid set of components which are lightly styled out of the box for light/dark mode. Instead of npm installing the library, you copy the components into your code base (via an npm command), customize as you see fit, and you own the code going forward. Note that this lib uses Tailwind for CSS, so I'd suggest only using it if you go with that though. Another nice benefit to this library is that it's extremely vibe-coding friendly (check out v0.dev which uses shadcn/ui for all components generated via AI). It's really innovative and many front-end devs are moving to it in droves.
  2. Traditional npm-installed libs. These are React component libs like Material UI, Chakra UI, Ant Design, Mantine. These libs do the trick and are much better than writing your own. They come with pretty good base styles which give you some customization for a price of maintenance going forward.

I use Ant Design in my project only because that's what was already in place when I joined the company. Otherwise I would've used shadcn/ui and Tailwind instead for these reasons. It's a pain overriding ant styles everywhere to get our Channel99 look and feel. Whenever I need to npm install antd@latest I always get a little nervous that something will break because my overrides aren't guaranteed to work and I don't love how large Ant is in my bundle...

Need localization?

I use lingui, but have heard i18next is really good (I personally don't recommend FormatJS). My product don't require localization currently, but I just wrap all my strings with <Trans>Something I need translated</Trans> and put together a simple build script to extract english strings from the app. When we get a translation crew at some point, I'll give them the english file, they can send me translations in French, Spanish, or whatever, and I load fr-FR.json at runtime instead of en-US.json and the app is localized. The hardest part of localization is wrapping every string. I decided to just incur a tiny amount of debt now to avoid a month-long project later to wrap every string in my app with <Trans> components.

Need a Date library?

I've been using dayjs, but if I started over I'd probably look heavily at date-fns instead. Don't use Moment. It's pretty bloated. Plus, dayjs has the exact same API if your team is already familiar with Moment and it weighs in at a fraction of the bundle size.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment