”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 从零到发布:我们 Remix 驱动的开发之旅的主要收获

从零到发布:我们 Remix 驱动的开发之旅的主要收获

发布于2024-11-13
浏览:491

Around six months ago, I made what some would say is a bold decision by choosing Remix as the foundation for our company's web application. Fast forward to today, and I think it's time to take a step back and reflect on the choices we made. I'll go over the main infrastructure decisions made and sprinkle a bit of practical usage examples on the way.

So, without further ado, let’s jump straight into the highlights and lowlights of this journey — a mix of satisfaction and lessons learned.

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Remix (or should I say React Router?)

Highlight: Remix

This is probably the "riskiest" infrastructure decision I made at that time, as Remix was not remotely as popular as NextJS and there weren't many examples of big enterprises using Remix to my knowledge.
Fast forward to today - ChatGPT migrated from Next to Remix just a few days ago!

As I detail in my previous article, I chose Remix for many reasons, some being its simplicity, the "full-stack" aspect (namely, utilizing the remix server as a "backend for frontend") and its great abstractions for routing, data fetching and mutations.

Fortunately, Remix delivered ? The framework is intuitive, easy to learn and teach others and ensures best practices are being used, making both writing code and testing it straightforward.

A few months into working with Remix, they announced the official merge with React Router, which I hope will persuade even more people to use it, just like their move to vite did.

It became clear to me in many occasions that Remix was the right call. I'll give one practical example I tackled lately - using a single logger instance in the remix server to be able to log and trace actions and errors across the entire app to enhance our monitoring abilities. The implementation was very straight-forward:

Step 1 - create your logger (in my case I used winston, which works great with Datadog that we use for monitoring)

Step 2 - add your logger to the server's load context (in my case it was express):

app.all(
  '*',
  createRequestHandler({
    getLoadContext: () => ({
      logger,
      // add any other context variables here
    }),
    mode: MODE,
    // ...
  }),
);

Step 3 (for typescript users) - update Remix's default type definitions to include the logger in the app load context

import '@remix-run/node';
import { type Logger } from 'winston';

declare module '@remix-run/node' {
  interface AppLoadContext {
    logger: Logger;
  }
}

Step 4 - use the logger as you wish in any route's loader or action!

export async function action({ request, context }: ActionFunctionArgs) {

  try {
    await someAction();
  } catch (e) {
    context.logger.error(e);
  }
}

Before we conclude this section, I do wish to say that there are also things I wish Remix had but they don't yet, like an implementation of RSC for streaming data/components, and Route Middlewares which would be great for authentication/authorization. Fortunately, it looks like these things (and other cool features) are prioritized in their roadmap, so hopefully we could get them soon!

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Tanstack Query. An all-time favorite

Highlight: React Query

Choosing @tanstack/react-query was an easy decision for me, based on my past positive experiences, and it didn’t disappoint this time either. The API is versatile, extendable, and unopinionated in the best way — making it easy to integrate with other tools.

I like it so much that I chose it knowing our internal API is GraphQL-based, instead of the more obvious choice that is Apollo Client. There are many reasons as to why: Tanstack Query has an excellent API, it’s significantly more lightweight than Apollo, and because I didn’t want to depend on a tool that’s heavily tailored to a specific technology like GraphQL, in case we ever need to switch or incorporate other technologies.

Plus, since we're using Remix, I could fully utilize Tanstack Query’s SSR capabilities — prefetching queries on the server-side while still maintaining the ability to mutate, invalidate, or refetch these queries on the client side. Here's a simplified example:

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();
  const { data } = useQuery(someDataQuery);

  return (
           
             
           />
  );
}

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Tailwind CSS

Highlight: Tailwind

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,
  );
};

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!

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Playwright - makes writing e2e tests fun

Highlight: Playwright

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!

From Zero to Launch: Key Takeaways from Our Remix-Powered Development Journey

Tanstack Table vs AG Grid

Lowlight: (Starting with) Tanstack Table

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

element with dynamic sizing and scrolling on its body only, without resorting to clunky solutions? ?).

I soon realized that to deliver my feature fast and provide a good user experience, I needed a table with built-in features like pagination, column resizing and row auto-sizing, and I preferred having those out of the box over full control of the UI rendering. Additionally, since the table only appears after a query is run, I could lazy load it, making the bundle size less of a concern.

I highly recommend using the AG Grid theme builder to customize AG Grid according to your preferences/design system. And, for those using Cypress for their testing purposes - I found this cool plugin that abstracts AG Grid to easily interact with it in tests (sadly I could not find the same for Playwright ?)

Final thoughts

Looking back, I definitely feel a sense of pride in what we’ve accomplished. Not every decision was perfect, but taking the time to research and find the most fitting solution was worth it. And when things didn’t go as planned - it challenged us to think critically and adapt quickly, which is important no less.

Please let me know in the comments if there’s something you’d like to see explored further in future articles.
Here’s to more lessons learned, personal growth and having fun along the way ?

版本声明 本文转载于:https://dev.to/n1tzan/from-zero-to-launch-key-takeaways-from-our-remix-powered-development-journey-3ph7?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • Java是否允许多种返回类型:仔细研究通用方法?
    Java是否允许多种返回类型:仔细研究通用方法?
    在Java中的多个返回类型:一种误解类型:在Java编程中揭示,在Java编程中,Peculiar方法签名可能会出现,可能会出现,使开发人员陷入困境,使开发人员陷入困境。 getResult(string s); ,其中foo是自定义类。该方法声明似乎拥有两种返回类型:列表和E。但这确实是如此吗...
    编程 发布于2025-07-25
  • 为什么使用Firefox后退按钮时JavaScript执行停止?
    为什么使用Firefox后退按钮时JavaScript执行停止?
    导航历史记录问题:JavaScript使用Firefox Back Back 此行为是由浏览器缓存JavaScript资源引起的。要解决此问题并确保在后续页面访问中执行脚本,Firefox用户应设置一个空功能。 警报'); }; alert('inline Alert')...
    编程 发布于2025-07-25
  • 使用jQuery如何有效修改":after"伪元素的CSS属性?
    使用jQuery如何有效修改":after"伪元素的CSS属性?
    在jquery中了解伪元素的限制:访问“ selector 尝试修改“:”选择器的CSS属性时,您可能会遇到困难。 This is because pseudo-elements are not part of the DOM (Document Object Model) and are th...
    编程 发布于2025-07-25
  • 在JavaScript中如何并发运行异步操作并正确处理错误?
    在JavaScript中如何并发运行异步操作并正确处理错误?
    同意操作execution 在执行asynchronous操作时,相关的代码段落会遇到一个问题,当执行asynchronous操作:此实现在启动下一个操作之前依次等待每个操作的完成。要启用并发执行,需要进行修改的方法。 第一个解决方案试图通过获得每个操作的承诺来解决此问题,然后单独等待它们: co...
    编程 发布于2025-07-25
  • 您可以使用CSS在Chrome和Firefox中染色控制台输出吗?
    您可以使用CSS在Chrome和Firefox中染色控制台输出吗?
    在javascript console 中显示颜色是可以使用chrome的控制台显示彩色文本,例如红色的redors,for for for for错误消息?回答是的,可以使用CSS将颜色添加到Chrome和Firefox中的控制台显示的消息(版本31或更高版本)中。要实现这一目标,请使用以下模...
    编程 发布于2025-07-25
  • Java中Lambda表达式为何需要“final”或“有效final”变量?
    Java中Lambda表达式为何需要“final”或“有效final”变量?
    Lambda Expressions Require "Final" or "Effectively Final" VariablesThe error message "Variable used in lambda expression shou...
    编程 发布于2025-07-25
  • 如何避免Go语言切片时的内存泄漏?
    如何避免Go语言切片时的内存泄漏?
    ,a [j:] ...虽然通常有效,但如果使用指针,可能会导致内存泄漏。这是因为原始的备份阵列保持完整,这意味着新切片外部指针引用的任何对象仍然可能占据内存。 copy(a [i:] 对于k,n:= len(a)-j i,len(a); k
    编程 发布于2025-07-25
  • PHP阵列键值异常:了解07和08的好奇情况
    PHP阵列键值异常:了解07和08的好奇情况
    PHP数组键值问题,使用07&08 在给定数月的数组中,键值07和08呈现令人困惑的行为时,就会出现一个不寻常的问题。运行print_r($月)返回意外结果:键“ 07”丢失,而键“ 08”分配给了9月的值。此问题源于PHP对领先零的解释。当一个数字带有0(例如07或08)的前缀时,PHP将其...
    编程 发布于2025-07-25
  • 为什么PHP的DateTime :: Modify('+1个月')会产生意外的结果?
    为什么PHP的DateTime :: Modify('+1个月')会产生意外的结果?
    使用php dateTime修改月份:发现预期的行为在使用PHP的DateTime类时,添加或减去几个月可能并不总是会产生预期的结果。正如文档所警告的那样,“当心”这些操作的“不像看起来那样直观。 ; $ date->修改('1个月'); //前进1个月 echo $ date->...
    编程 发布于2025-07-25
  • 如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    使用http request 上传文件上传到http server,同时也提交其他参数,java.net.net.urlconnection and Multipart/form-data Encoding是普遍的。 Here's a breakdown of the process:Mu...
    编程 发布于2025-07-25
  • PHP未来:适应与创新
    PHP未来:适应与创新
    PHP的未来将通过适应新技术趋势和引入创新特性来实现:1)适应云计算、容器化和微服务架构,支持Docker和Kubernetes;2)引入JIT编译器和枚举类型,提升性能和数据处理效率;3)持续优化性能和推广最佳实践。 引言在编程世界中,PHP一直是网页开发的中流砥柱。作为一个从1994年就开始发展...
    编程 发布于2025-07-25
  • 如何使用Depimal.parse()中的指数表示法中的数字?
    如何使用Depimal.parse()中的指数表示法中的数字?
    在尝试使用Decimal.parse(“ 1.2345e-02”中的指数符号表示法表示的字符串时,您可能会遇到错误。这是因为默认解析方法无法识别指数符号。 成功解析这样的字符串,您需要明确指定它代表浮点数。您可以使用numbersTyles.Float样式进行此操作,如下所示:[&& && && ...
    编程 发布于2025-07-25
  • 版本5.6.5之前,使用current_timestamp与时间戳列的current_timestamp与时间戳列有什么限制?
    版本5.6.5之前,使用current_timestamp与时间戳列的current_timestamp与时间戳列有什么限制?
    在时间戳列上使用current_timestamp或MySQL版本中的current_timestamp或在5.6.5 此限制源于遗留实现的关注,这些限制需要对当前的_timestamp功能进行特定的实现。 创建表`foo`( `Productid` int(10)unsigned not n...
    编程 发布于2025-07-25
  • 在Python中如何创建动态变量?
    在Python中如何创建动态变量?
    在Python 中,动态创建变量的功能可以是一种强大的工具,尤其是在使用复杂的数据结构或算法时,Dynamic Variable Creation的动态变量创建。 Python提供了几种创造性的方法来实现这一目标。利用dictionaries 一种有效的方法是利用字典。字典允许您动态创建密钥并分...
    编程 发布于2025-07-25
  • 为什么我在Silverlight Linq查询中获得“无法找到查询模式的实现”错误?
    为什么我在Silverlight Linq查询中获得“无法找到查询模式的实现”错误?
    查询模式实现缺失:解决“无法找到”错误在银光应用程序中,尝试使用LINQ建立错误的数据库连接的尝试,无法找到以查询模式的实现。”当省略LINQ名称空间或查询类型缺少IEnumerable 实现时,通常会发生此错误。 解决问题来验证该类型的质量是至关重要的。在此特定实例中,tblpersoon可能需...
    编程 发布于2025-07-25

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3