Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Malware / Trojaner / VirenAI Agents Are Becoming a New Malware Distribution Channel(23.09.2026 um 09:44 Uhr)
Sichere ProgrammierungBuilding In-Browser Private Tools: When the Server Is the Liability(23.09.2026 um 08:54 Uhr)
Sichere ProgrammierungYour Order Fulfillment Workflow Is One 24-Hour Wait Away From Chaos(23.09.2026 um 08:54 Uhr)
Sichere Programmierungflet media library(23.09.2026 um 08:54 Uhr)
Sichere ProgrammierungRunning Lightdash on Snowpark Container Services(23.09.2026 um 08:55 Uhr)
Sichere ProgrammierungThe Impossible Filter Gallery Transition in CSS Only(23.09.2026 um 08:59 Uhr)
Sichere ProgrammierungVerifiable Data > Claimed Data: What i'm Trying to do with Ori's List(23.09.2026 um 09:08 Uhr)
Malware / Trojaner / VirenAI Agents Are Becoming a New Malware Distribution Channel(23.09.2026 um 09:44 Uhr)
Sichere ProgrammierungBuilding In-Browser Private Tools: When the Server Is the Liability(23.09.2026 um 08:54 Uhr)
Sichere ProgrammierungYour Order Fulfillment Workflow Is One 24-Hour Wait Away From Chaos(23.09.2026 um 08:54 Uhr)
Sichere Programmierungflet media library(23.09.2026 um 08:54 Uhr)
Sichere ProgrammierungRunning Lightdash on Snowpark Container Services(23.09.2026 um 08:55 Uhr)
Sichere ProgrammierungThe Impossible Filter Gallery Transition in CSS Only(23.09.2026 um 08:59 Uhr)
Sichere ProgrammierungVerifiable Data > Claimed Data: What i'm Trying to do with Ori's List(23.09.2026 um 09:08 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

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

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…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

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.



Image description


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!



Image description


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

return (
<HydrationBoundary state={dehydratedState}>
<SomeComponent data={data} />
</HydrationBoundary />
);
}






Image description


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<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!



Image description


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!



Image description


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 <table> 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 💃

Ähnliche Beiträge
🔍 Verwandte News

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

Thematisch verwandte Begriffe: From, Zero, Launch, Takeaways · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-96258 | A vulnerability has been found in onSite internet GmbH Auktion NG Auktio…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick