Maison > Article > interface Web > De zéro au lancement : points à retenir de notre parcours de développement basé sur Remix
Il y a environ six mois, j'ai pris ce que certains qualifieraient de décision audacieuse en choisissant Remix comme base de l'application Web de notre entreprise. Avance rapide jusqu’à aujourd’hui, et je pense qu’il est temps de prendre du recul et de réfléchir aux choix que nous avons faits. Je passerai en revue les principales décisions prises en matière d'infrastructure et saupoudrerai quelques exemples d'utilisation pratiques en chemin.
Alors, sans plus tarder, passons directement aux points forts et aux points faibles de ce voyage – un mélange de satisfaction et de leçons apprises.
C'est probablement la décision d'infrastructure la plus "risquée" que j'ai prise à cette époque, car Remix n'était pas aussi populaire que NextJS et il n'y avait pas beaucoup d'exemples de grandes entreprises utilisant Remix à ma connaissance.
Avance rapide jusqu'à aujourd'hui : ChatGPT a migré de Next vers Remix il y a quelques jours !
Comme je le détaille dans mon article précédent, j'ai choisi Remix pour de nombreuses raisons, certaines étant sa simplicité, l'aspect "full-stack" (à savoir, utiliser le serveur remix comme "backend pour frontend") et ses grandes abstractions pour routage, récupération de données et mutations.
Heureusement, Remix livré ? Le cadre est intuitif, facile à apprendre et à enseigner aux autres et garantit que les meilleures pratiques sont utilisées, ce qui rend l'écriture du code et son test simples.
Quelques mois après avoir travaillé avec Remix, ils ont annoncé la fusion officielle avec React Router, qui, je l'espère, persuadera encore plus de personnes de l'utiliser, tout comme leur passage à vite l'a fait.
Il m'est apparu clairement à plusieurs reprises que Remix était la bonne décision. Je vais donner un exemple pratique que j'ai abordé récemment : utiliser une seule instance d'enregistreur dans le serveur de remix pour pouvoir enregistrer et tracer les actions et les erreurs dans l'ensemble de l'application afin d'améliorer nos capacités de surveillance. La mise en œuvre a été très simple :
Étape 1 - créez votre enregistreur (dans mon cas j'ai utilisé Winston, qui fonctionne très bien avec Datadog que nous utilisons pour la surveillance)
Étape 2 - ajoutez votre logger au contexte de chargement du serveur (dans mon cas c'était express) :
app.all( '*', createRequestHandler({ getLoadContext: () => ({ logger, // add any other context variables here }), mode: MODE, // ... }), );
Étape 3 (pour les utilisateurs dactylographiés) - mettre à jour les définitions de type par défaut de Remix pour inclure l'enregistreur dans le contexte de chargement de l'application
import '@remix-run/node'; import { type Logger } from 'winston'; declare module '@remix-run/node' { interface AppLoadContext { logger: Logger; } }
Étape 4 : utilisez l'enregistreur comme vous le souhaitez dans le chargeur ou l'action de n'importe quel itinéraire !
export async function action({ request, context }: ActionFunctionArgs) { try { await someAction(); } catch (e) { context.logger.error(e); } }
Avant de conclure cette section, je tiens à dire qu'il y a aussi des choses que j'aurais aimé que Remix ait mais ce n'est pas encore le cas, comme une implémentation de RSC pour le streaming de données/composants, et des Route Middlewares qui seraient parfaits pour l'authentification. /autorisation. Heureusement, il semble que ces éléments (et d'autres fonctionnalités intéressantes) soient prioritaires dans leur feuille de route, alors j'espère que nous pourrons les obtenir bientôt !
Choisir @tanstack/react-query a été une décision facile pour moi, basée sur mes expériences positives passées, et cela n'a pas déçu cette fois non plus. L'API est polyvalente, extensible et sans opinion de la meilleure façon possible, ce qui la rend facile à intégrer à d'autres outils.
Je l'aime tellement que je l'ai choisi sachant que notre API interne est basée sur GraphQL, au lieu du choix plus évident qu'est Apollo Client. Il y a plusieurs raisons à cela : Tanstack Query possède une excellente API, elle est nettement plus légère qu'Apollo, et parce que je ne voulais pas dépendre d'un outil fortement adapté à une technologie spécifique comme GraphQL, au cas où nous aurions besoin de le faire. changer ou intégrer d’autres technologies.
De plus, puisque nous utilisons Remix, j'ai pu utiliser pleinement les capacités SSR de Tanstack Query — prélecture des requêtes côté serveur tout en conservant la possibilité de muter, d'invalider ou de récupérer ces requêtes côté client. Voici un exemple simplifié :
import { dehydrate, QueryClient, HydrationBoundary, useQuery } from '@tanstack/react-query'; import { json, useLoaderData } from '@remix-run/react'; const someDataQuery = { queryKey: ['some-data'], queryFn: () => fetchSomeData() } export async function loader() { const queryClient = new QueryClient(); try { await queryClient.fetchQuery(someDataQuery); return json({ dehydrate: dehydrate(queryClient) }); } catch (e) { // decide whether to handle the error or continue to // render the page and retry the query in the client } } export default function MyRouteComponent() { const { dehydratedState } = useLoaderData<typeof loader>(); const { data } = useQuery(someDataQuery); return ( <HydrationBoundary state={dehydratedState}> <SomeComponent data={data} /> </HydrationBoundary /> ); }
I was initially skeptical about Tailwind, having never used it before, and because I didn’t quite understand the hype (it seemed to me at first just like syntactic sugar over CSS). However, I decided to give it a try because of its strong recommendations and popularity within the community, and I’m really glad I did. Tailwind’s utility-first approach made it incredibly easy to build a consistent and robust design system right from the start, which, looking back, was a total game changer.
It also pairs perfectly with shadcn, which we used, and together they allowed me to deliver quickly while keeping everything modular and easy to modify later on - a crucial advantage in a startup environment.
I also really like how easy it is to customize tailwind's theme to your needs - for example, overriding tailwind's default scheme:
First, define your colors as variable's under tailwind's main .css file:
@tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { /* define the primitive design system tokens */ --colors-blue-100: hsl(188 76% 90%); --colors-blue-200: hsl(187 63% 82%); --colors-blue-25: hsl(185 100% 98%); --colors-blue-300: hsl(190 52% 74%); --colors-blue-400: hsl(190 52% 61%); --colors-blue-50: hsl(188 92% 95%); --colors-blue-500: hsl(190 74% 39%); --colors-blue-600: hsl(191 77% 34%); --colors-blue-700: hsl(190 51% 35%); --colors-blue-800: hsl(191 52% 29%); --colors-blue-900: hsl(190 51% 23%); --colors-blue-950: hsl(190 52% 17%); --colors-gray-100: hsl(0 0 90%); --colors-gray-200: hsl(0 0 85%); --colors-gray-25: hsl(0 0 98%); --colors-gray-300: hsl(0 0 73%); --colors-gray-400: hsl(0 1% 62%); --colors-gray-50: hsl(0 0 94%); --colors-gray-500: hsl(0 0% 53%); --colors-gray-600: hsl(0 0 44%); --colors-gray-700: hsl(0 0 36%); --colors-gray-800: hsl(0 2% 28%); --colors-gray-900: hsl(0 0 20%); --colors-gray-950: hsl(0 0 5%); --colors-red-100: hsl(4 93% 94%); --colors-red-200: hsl(3 96% 89%); --colors-red-25: hsl(12 100% 99%); --colors-red-300: hsl(4 96% 80%); --colors-red-400: hsl(4 92% 69%); --colors-red-50: hsl(5 86% 97%); --colors-red-500: hsl(4 88% 61%); --colors-red-600: hsl(4 74% 49%); --colors-red-700: hsl(4 76% 40%); --colors-red-800: hsl(4 72% 33%); --colors-red-900: hsl(8 65% 29%); --colors-red-950: hsl(8 75% 19%); /* ... */ /* define the semantic design system tokens */ --primary-light: var(--colors-blue-200); --primary: var(--colors-blue-600); --primary-dark: var(--colors-blue-800); --primary-hover: var(--colors-blue-50); --text-default-primary: var(--colors-gray-700); --text-default-secondary: var(--colors-gray-800); --text-default-tertiary: var(--colors-gray-900); --text-default-disabled: var(--colors-gray-300); --text-default-read-only: var(--colors-gray-400); --disabled: var(--colors-gray-300); --tertiary: var(--colors-gray-50); /* ... */ } }
Then, extend Tailwind's default theme via the tailwind config file:
import { type Config } from 'tailwindcss'; const ColorTokens = { BLUE: 'blue', GRAY: 'gray', RED: 'red', } as const; const generateColorScale = (colorName: string) => { const scales = [25, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]; return scales.reduce( (acc, scale) => { acc[scale] = `var(--colors-${colorName}-${scale})`; return acc; }, {} as Record<string, string>, ); }; export const customColors = Object.values(ColorTokens).reduce((acc, color) => { return { ...acc, [color]: generateColorScale(color), }; }, {}); const config = { // ... additional config theme: { extend: { colors: customColors }, }, } satisfies Config; export default config;
This is just the tip of the iceberg - you can go on to define custom spacing, text sizing and much more!
Previously using Cypress, I was inclined to choose it, but I kept hearing hype around Playwright and figured I'll research it extensively before making a decision. After comparing Playwright with Cypress, it was clear Playwright is the right choice to make - the fact it comes with parallel execution out of the box, the broader browser support, running times and debugging capabilities - all made Playwright the obvious choice.
And, while this is very subjective, I like Playwright's syntax much better. I find it similar to React Testing Library's syntax, which I like, and I tend to think the tests are a lot more readable, with the asynchronous aspect of the tests being very straight forward, unlike the syntax of Cypress that can cause tests to feel bloated by .then() statements and subsequent indentations.
I think my favorite feature of Playwright is their implementation of Test Fixtures. They provide a clean way to initialize and reuse resources like page objects, making tests more modular and maintainable. Make sure to check out the above link to learn more about it!
First off, let me clarify — @tanstack/react-table is a fantastic tool, which is why I was inclined to choose it in the first place, but it wasn’t the best fit for my particular use case. The very features that make it great, like its small bundle size and customizable API, ended up being less relevant to our needs than I originally thought. Despite having full control of the rendering of the Table, I was having some issues aligning its scrolling behavior to our desired outcome (why is it still not possible in 2024 to have a