DEV Community

Cover image for Build a Full Stack App with Next.js, Tailwind, tRPC and Prisma ORM
Francisco Mendes
Francisco Mendes

Posted on

Build a Full Stack App with Next.js, Tailwind, tRPC and Prisma ORM

When we create a TypeScript project that has both a Rest Api and a web app, it becomes challenging to keep type definitions concise in the long run.

If we created a GraphQL Api, the conversation might change because we can use code generation, but we still have to maintain the schema on the backend side.

So basically, in both options, we always have to maintain a schema or some sort of type definition.

Introduction

This is where tRPC comes in, with this toolkit it is possible to create a totally type safe application by only using inference. When we made a small change in the backend, we ended up having those same changes reflected in the frontend.

Prerequisites

Before going further, you need:

  • Node
  • TypeScript
  • Next.js
  • Tailwind
  • NPM

In addition, you are expected to have basic knowledge of these technologies.

Getting Started

Project setup

Let's setup next.js and navigate into the project directory:

npx create-next-app@latest --ts grocery-list
cd grocery-list
Enter fullscreen mode Exit fullscreen mode

In tsconfig.json we will add a path alias to make it easier to work with relative paths:

// @/tsconfig.json
{
  "compilerOptions": {
    // ...
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ],
    }
  },
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Install Tailwind CSS:

npm install @fontsource/poppins
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

In the file tailwind.config.js add the paths to the pages and components folders:

// @/tailwind.config.js
module.exports = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx}",
    "./src/components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode

Now let's add the Tailwind directives to our globals.css:

/* @/src/styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

* {
    font-family: "Poppins";
  }
Enter fullscreen mode Exit fullscreen mode

As you may have noticed, all our source code, including the styles, will be inside the /src folder.

Setup Prisma

First of all let's install the necessary dependencies:

npm install prisma
Enter fullscreen mode Exit fullscreen mode

Now let's initialize the prisma setup:

npx prisma init
Enter fullscreen mode Exit fullscreen mode

And let's add the following schema to our schema.prisma:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

model GroceryList {
  id      Int      @id @default(autoincrement())
  title   String
  checked Boolean? @default(false)
}
Enter fullscreen mode Exit fullscreen mode

With the schema defined, you can run our first migration:

npx prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode

Finally we can install the prisma client:

npm install @prisma/client
Enter fullscreen mode Exit fullscreen mode

With the base configuration of our project complete, we can move on to the next step.

Configure tRPC

First of all, let's make sure that tsconfig.json has strict mode enabled:

// @/tsconfig.json
{
  "compilerOptions": {
    // ...
    "strict": true
  },
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Then we can install the following dependencies:

npm install @trpc/client @trpc/server @trpc/react @trpc/next zod react-query
Enter fullscreen mode Exit fullscreen mode

With our dependencies installed we can create the /server folder and we can create our context.

The context is used to pass contextual data to all router resolvers. And in our context we will just pass our prism client instance.

// @/src/server/context.ts
import * as trpc from "@trpc/server";
import * as trpcNext from "@trpc/server/adapters/next";
import { PrismaClient } from "@prisma/client";

export async function createContext(opts?: trpcNext.CreateNextContextOptions) {
const prisma = new PrismaClient();

return { prisma };
}

export type Context = trpc.inferAsyncReturnType<typeof createContext>;
Enter fullscreen mode Exit fullscreen mode

With our context created (createContext()) and the data types inferred from it (Context), we can move on to defining our router, but before that it is important to keep in mind that:

  • An endpoint is called a procedure;
  • A procedure can have two types of operations (query and mutation);
  • Queries are responsible for fetching data, while mutations are responsible for making changes to the data (server-side).

With these points in mind we can now define our router:

// @/src/server/router.ts
import * as trpc from "@trpc/server";
import { z } from "zod";

import { Context } from "./context";

export const serverRouter = trpc
  .router<Context>()
  .query("findAll", {
    resolve: async ({ ctx }) => {
      return await ctx.prisma.groceryList.findMany();
    },
  })
  .mutation("insertOne", {
    input: z.object({
      title: z.string(),
    }),
    resolve: async ({ input, ctx }) => {
      return await ctx.prisma.groceryList.create({
        data: { title: input.title },
      });
    },
  })
  .mutation("updateOne", {
    input: z.object({
      id: z.number(),
      title: z.string(),
      checked: z.boolean(),
    }),
    resolve: async ({ input, ctx }) => {
      const { id, ...rest } = input;

      return await ctx.prisma.groceryList.update({
        where: { id },
        data: { ...rest },
      });
    },
  })
  .mutation("deleteAll", {
    input: z.object({
      ids: z.number().array(),
    }),
    resolve: async ({ input, ctx }) => {
      const { ids } = input;

      return await ctx.prisma.groceryList.deleteMany({
        where: {
          id: { in: ids },
        },
      });
    },
  });

export type ServerRouter = typeof serverRouter;
Enter fullscreen mode Exit fullscreen mode

Based on the previous snippet, you may have noticed the following:

  • The data type of our context was used as a generic in our router so that we have the typed context object (in order to have access to our prisma instance);
  • Our backend has a total of four procedures;
  • We exported our router (serverRouter) and its data type (ServerRouter).

With our router configured, we need to create a API route from Next.js to which we will add our handler api. In our handler api we will pass our router and our context (which is invoked on every request).

// @/src/pages/api/trpc/[trpc].ts
import * as trpcNext from "@trpc/server/adapters/next";

import { serverRouter } from "@/server/router";
import { createContext } from "@/server/context";

export default trpcNext.createNextApiHandler({
  router: serverRouter,
  createContext,
});
Enter fullscreen mode Exit fullscreen mode

Now it's time to configure the _app.tsx file as follows:

// @/src/pages/_app.tsx
import "../styles/globals.css";
import "@fontsource/poppins";
import { withTRPC } from "@trpc/next";
import { AppType } from "next/dist/shared/lib/utils";
import type { ServerRouter } from "@/server/router";

const App: AppType = ({ Component, pageProps }) => {
  return <Component {...pageProps} />;
};

export default withTRPC<ServerRouter>({
  config({ ctx }) {
    const url = process.env.VERCEL_URL
      ? `https://${process.env.VERCEL_URL}/api/trpc`
      : "http://localhost:3000/api/trpc";

    return { url };
  },
  ssr: true,
})(App);
Enter fullscreen mode Exit fullscreen mode

Then we will be create the tRPC hook, to which we will add the data type of our router as a generic on the createReactQueryHooks() function, so that we can make api calls:

// @/src/utils/trpc.ts
import type { ServerRouter } from "@/server/router";
import { createReactQueryHooks } from "@trpc/react";

export const trpc = createReactQueryHooks<ServerRouter>();
Enter fullscreen mode Exit fullscreen mode

Create the Frontend

First let's deal with the components of our application, to be simpler I'll put everything in a single file in the /components folder.

Starting with the card, let's create the card's container, header and content:

// @/src/components/index.tsx
import React, { memo } from "react";
import type { NextPage } from "next";
import { GroceryList } from "@prisma/client";

interface CardProps {
  children: React.ReactNode;
}

export const Card: NextPage<CardProps> = ({ children }) => {
  return (
    <div className="h-screen flex flex-col justify-center items-center bg-slate-100">
      {children}
    </div>
  );
};

export const CardContent: NextPage<CardProps> = ({ children }) => {
  return (
    <div className="bg-white w-5/6 md:w-4/6 lg:w-3/6 xl:w-2/6 rounded-lg drop-shadow-md">
      {children}
    </div>
  );
};

interface CardHeaderProps {
  title: string;
  listLength: number;
  clearAllFn?: () => void;
}

export const CardHeader: NextPage<CardHeaderProps> = ({
  title,
  listLength,
  clearAllFn,
}) => {
  return (
    <div className="flex flex-row items-center justify-between p-3 border-b border-slate-200">
      <div className="flex flex-row items-center justify-between">
        <h1 className="text-base font-medium tracking-wide text-gray-900 mr-2">
          {title}
        </h1>
        <span className="h-5 w-5 bg-blue-200 text-blue-600 flex items-center justify-center rounded-full text-xs">
          {listLength}
        </span>
      </div>
      <button
        className="text-sm font-medium text-gray-600 underline"
        type="button"
        onClick={clearAllFn}
      >
        Clear all
      </button>
    </div>
  );
};

// ...
Enter fullscreen mode Exit fullscreen mode

Now that we've created our card, we can create the components of our list:

// @/src/components/index.tsx
import React, { memo } from "react";
import type { NextPage } from "next";
import { GroceryList } from "@prisma/client";

// ...

export const List: NextPage<CardProps> = ({ children }) => {
  return <div className="overflow-y-auto h-72">{children}</div>;
};

interface ListItemProps {
  item: GroceryList;
  onUpdate?: (item: GroceryList) => void;
}

const ListItemComponent: NextPage<ListItemProps> = ({ item, onUpdate }) => {
  return (
    <div className="h-12 border-b flex items-center justify-start px-3">
      <input
        type="checkbox"
        className="w-4 h-4 border-gray-300 rounded mr-4"
        defaultChecked={item.checked as boolean}
        onChange={() => onUpdate?.(item)}
      />
      <h2 className="text-gray-600 tracking-wide text-sm">{item.title}</h2>
    </div>
  );
};

export const ListItem = memo(ListItemComponent);

// ...
Enter fullscreen mode Exit fullscreen mode

Finally, just create our form to add new elements to the list:

// @/src/components/index.tsx
import React, { memo } from "react";
import type { NextPage } from "next";
import { GroceryList } from "@prisma/client";

// ...

interface CardFormProps {
  value: string;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  submit: () => void;
}

export const CardForm: NextPage<CardFormProps> = ({
  value,
  onChange,
  submit,
}) => {
  return (
    <div className="bg-white w-5/6 md:w-4/6 lg:w-3/6 xl:w-2/6 rounded-lg drop-shadow-md mt-4">
      <div className="relative">
        <input
          className="w-full py-4 pl-3 pr-16 text-sm rounded-lg"
          type="text"
          placeholder="Grocery item name..."
          onChange={onChange}
          value={value}
        />
        <button
          className="absolute p-2 text-white -translate-y-1/2 bg-blue-600 rounded-full top-1/2 right-4"
          type="button"
          onClick={submit}
        >
          <svg
            className="w-4 h-4"
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth="2"
              d="M12 6v6m0 0v6m0-6h6m-6 0H6"
            />
          </svg>
        </button>
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

And with everything ready, we can start working on our main page. Which can be as follows:

// @/src/pages/index.tsx
import type { NextPage } from "next";
import Head from "next/head";
import { useCallback, useState } from "react";
import { trpc } from "@/utils/trpc";

import {
  Card,
  CardContent,
  CardForm,
  CardHeader,
  List,
  ListItem,
} from "../components/Card";
import { GroceryList } from "@prisma/client";

const Home: NextPage = () => {
  const [itemName, setItemName] = useState<string>("");

  const { data: list, refetch } = trpc.useQuery(["findAll"]);
  const insertMutation = trpc.useMutation(["insertOne"], {
    onSuccess: () => refetch(),
  });
  const deleteAllMutation = trpc.useMutation(["deleteAll"], {
    onSuccess: () => refetch(),
  });
  const updateOneMutation = trpc.useMutation(["updateOne"], {
    onSuccess: () => refetch(),
  });

  const insertOne = useCallback(() => {
    if (itemName === "") return;

    insertMutation.mutate({
      title: itemName,
    });

    setItemName("");
  }, [itemName, insertMutation]);

  const clearAll = useCallback(() => {
    if (list?.length) {
      deleteAllMutation.mutate({
        ids: list.map((item) => item.id),
      });
    }
  }, [list, deleteAllMutation]);

  const updateOne = useCallback(
    (item: GroceryList) => {
      updateOneMutation.mutate({
        ...item,
        checked: !item.checked,
      });
    },
    [updateOneMutation]
  );

  return (
    <>
      <Head>
        <title>Grocery List</title>
        <meta name="description" content="Visit www.mosano.eu" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <Card>
          <CardContent>
            <CardHeader
              title="Grocery List"
              listLength={list?.length ?? 0}
              clearAllFn={clearAll}
            />
            <List>
              {list?.map((item) => (
                <ListItem key={item.id} item={item} onUpdate={updateOne} />
              ))}
            </List>
          </CardContent>
          <CardForm
            value={itemName}
            onChange={(e) => setItemName(e.target.value)}
            submit={insertOne}
          />
        </Card>
      </main>
    </>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

After all these steps in this article, the expected final result is as follows:

image

If you just want to clone the project and create your own version of this app, you can click on this link to access the repository for this article.

I hope you found this article helpful and I'll see you next time.

Discussion (41)

Collapse
brense profile image
Rense Bakker

Tailwind is evil, it pollutes your jsx code and it cannot do some of the basic stuff that UI frameworks offer these days, like media queries in js (useMediaQuery). Basically what Tailwind does is compile a huge list of classes before any js code is actually run. This means you cannot use any kind of variable value from js. The only way to do so, is by using CSS variables and change the value of those in runtime using JS. A very cumbersome approach, thats very difficult to understand for people who didnt write the code (or if you dont work on the project for a while). You cannot access any of the values used in Tailwind from JS, so you're completely locked into doing things "their way" (very opinionated framework). Modern UI frameworks offer ways to access the defined values, like your theme/pallete or the defined breakpoints.

All Tailwind does is create obscure classnames that you have to learn. Instead of using the CSS that you already know.

<div className="bg-white mt-4">
Enter fullscreen mode Exit fullscreen mode

If you refuse to use a modern UI framework. Why not just use the style property instead of Tailwind CSS...

<div style={{ background: 'white', marginTop: '1rem' }}>
Enter fullscreen mode Exit fullscreen mode

Oh no! It's a few characters longer! facepalm

Collapse
franciscomendes10866 profile image
Francisco Mendes Author • Edited on

I think the fact that Tailwind is utility-first is ideal for creating an application, whether it's web or mobile (I've used it for several months with React Native) if you have a specific design that you want to implement and the flexibility you have is incredible .

A lot of people like to comment on the fact that we have to memorize several classes, but over time it becomes totally natural when compared to other css frameworks.

Regarding the "pollution" of jsx and making it more difficult to read, there are several ways to accomplish this, the simplest is to create a variable outside the functional component and add styles to it (but you can also use css and scss modules, twin, etc). Another aspect is that we can also create our design system with tailwind, from colors, spacing, etc.

I don't put myself as a fan of anything, because in reality the stack and libs change from project to project, but I think it's incorrect to look only at some aspects and not at others (this discussion could take days).

Collapse
brense profile image
Rense Bakker

but over time it becomes totally natural

CSS is already natural. Why learn something else that offers no extra benefit. Thats just arrogant framework behavior if you ask me. Tailwind CSS is basically saying: "We're better than the people who came up with CSS, so we're just going to rename a small percentage of CSS syntax to custom classnames, cuz we're rebel bruh!"

there are several ways to accomplish this

If a framework forcefully introduces problems that you need to fix in order to stay anywhere near clean code principles, why use the framework at all... Its a bad framework. <- emphasis on period.

Another aspect is that we can also create our design system with tailwind

You can do that with modern UI frameworks aswell, most of them offer ways to define/override pretty much every aspect and actually offer good documentation on how to do so. For example in Mui: mui.com/material-ui/customization/...

And we havent even began to touch on the fact that you definitely do not want to run any a11y linting on an application using Tailwind.

The discussion only takes days because the Tailwind fanboys obsessively want to stay rebel and produce web applications that do not follow any web standard.

Thread Thread
natescode profile image
Nathan Hedglin

Because hating on a new approach you don't understand is arrogant behavior.

Read frontstuff.io/in-defense-of-utilit...

Thread Thread
natescode profile image
Nathan Hedglin

So using a tool dumbly makes it a dumb tool? I think that makes the user the dumb tool 😂

Thread Thread
brense profile image
Rense Bakker

Assuming someone doesnt understand Tailwind, without any arguments is arrogant behavior. I've given plenty of examples why I think Tailwind is an evil framework.

Assuming people are dumb and/or are not using your framework properly is also pretty arrogant...

It's not a new approach we we're taught to seperate CSS from JS code in 2006 and we've since moved on to greener pastures with the introduction of modern JS frameworks and UI frameworks.

Thread Thread
natescode profile image
Nathan Hedglin

you must be fun to work with /s.

  1. I linked a great article that rebuttals MANY points but you obviously prefer to just be rude back.
  2. I don't feel like arguing with rude people today.
  3. You can follow me, I'm working on a long post about Tailwind. Because people like you are too religious about CSS.
  4. You're mixing CSS and JavaScript with useMediaQuery, isn't that breaking separation of concerns? JSX mixes HTML and Javascript too.

Cheers! Peace out.

Thread Thread
brense profile image
Rense Bakker

You're mixing CSS and JavaScript

Yes, that interpretation of seperation concerns fell out of grace some time ago. Because we found out that you can develop much faster and less error prone, if you are not religious about seperating presentation from business logic.

Seperation of concerns is still important within business logic: "Modularity, and hence separation of concerns, is achieved by encapsulating information inside a section of code that has a well-defined interface"

We also follow a rule now that says: "Keep related things together" because it makes things easier to find and understand for new devs.

If you can link me to a Tailwind implementation with prebuilt UI components for React that respect a11y. I'd be willing to give it another try.

you must be fun to work with /s.

Yes I'm definitely fun to work with. Thank you for making another attack on my person because I dare to say Tailwind is evil.

Thread Thread
natescode profile image
Nathan Hedglin

Which specific A11y problems were you having?

Thread Thread
natescode profile image
Nathan Hedglin

"We also follow a rule now that says: "Keep related things together" because it makes things easier to find and understand for new devs."

You mean like utility classes? 😂

Thread Thread
brense profile image
Rense Bakker

All of them. Tailwind offers zero accessibility optimizations that modern UI framework do offer

Thread Thread
brense profile image
Rense Bakker

No, not like utility classes. An app is more than a presentation layer...

Thread Thread
natescode profile image
Nathan Hedglin

Please, I want a specific example. It'd help greatly with my research for my blog post.

Were you doing contrast stuff, reduce animation? I assume something with media queries.

Sorry for the personal attack. I appreciate your perspective.

Thread Thread
natescode profile image
Nathan Hedglin
Thread Thread
glenhayes profile image
Glen Hayes

Wanted to throw this in the mix, headlessui.com/ which is a project from the creators of tailwind. It is a library of unstyled accessible UI components.

Thread Thread
brense profile image
Rense Bakker

That is a good initiative, however I am still missing the most used unaccessible elements: inputs and buttons. And how many people using Tailwind are actually using headless UI though? I reckon not many, atleast I have not seen it out in the wild yet.

Thread Thread
natescode profile image
Nathan Hedglin

Inputs and buttons already are accessible given the appropriate type attribute and aria label or aria label by are defined.

Tailwind is young. It's a great approach to CSS but it's just CSS. If you really want a solid and accessible component library with it then it probably isn't the best choice for now.

Thread Thread
brense profile image
Rense Bakker

Yes, its not a good choice for a solid and accessible component library but that is what a lot of people are using it for. The discussion I always see is: should I use ant design, material ui or tailwind... I just dont see it as a good fit anymore to the way we work these days... All designers I've worked with in the past 3 years use some design system and 9/10 times thats practically already a copy of material design system, that can very easily be implemented in existing component libraries like mui by doing some small overrides or sometimes just changing the colors and units. If the business knew that UI component libraries exist, they would never pay for developers to build their own UI component library usingTailwind CSS...

Thread Thread
natescode profile image
Nathan Hedglin • Edited on

Tailwind is NOT a component library. Maybe that's where you were confused.

"If the business knew that UI component libraries exist, they would never pay for developers to build their own UI component library using Tailwind CSS"

False Businesses KNOW, but they often want to be special and have their own designs which are MUCH better done manually than butchering material UI or Boostraps' components CSS.

Yes, much of the time but not always. Plenty of projects I've been on wanted something completely unique. We didn't use Tailwind but that probably would have worked better.

My whole point is you think a tool is evil and users cocky when it isn't the right tool for YOU or that projects' requirements. That where you're wrong. Blaming the tool instead of yourself.

Material UI wouldn't work for many projects that I've been on. If it does then I'd suggest it or use it.

TL;DR use the right tool for the job. Don't blame the tool.

Thread Thread
brense profile image
Rense Bakker

I'm not sure what projects would benefit from bad accessibility and bad user experience. Design systems exist for a reason, they take a lot of work and consideration. I feel like you're quick to jump to the conclusion that using an existing design system makes it impossible to deliver a unique design according to what the business wants. I also feel like tailwind people like to pretend that its not extremely easy to customize ui component libraries like mui, that make use of material design system for example. You can make it completely custom to the point where you cant tell its based on material design system...

Yes, use the right tool for the job and also, dont reinvent the wheel.

Thread Thread
natescode profile image
Nathan Hedglin • Edited on

Ok. So Tailwind isn't evil and it's users aren't cocky then? You admit you used a CSS library to replace a UI component library, which was your fault not Tailwinds?

I'm not too quick to say they aren't customizable. I HAVE customized them successfully. I agree, MOST of the time that is the way to go. I've also seen where the business rightfully rejected the idea.

No, you HAVE TO make it accessible manually if you make your own design system, duh. That's a big cost and should be justified; most often it isn't of course.

Edit

Also, you're making a false dichotomy: Tailwind OR UI library. MUI supports Tailwind IF one wants, not saying that's the way to go.

Peace out ✌️

Thread Thread
brense profile image
Rense Bakker

You admit you used a CSS library to replace a UI component library

I did no such thing, I end up in teams sometimes where a decision was already made on what tools they want to use, usually without good justification.

Also, you're making a false dichotomy

I'm not, the Tailwind people are. Everytime I come into a team working with Tailwind, this is the exact comparison they make: we either use ant design, material ui or tailwind and then they chose Tailwind and I have to fix all their accessibility issues and explain to management why we can't deliver because we have to build our own components first, instead of reusing what already exists in so many UI component libraries out there. Infact I've even been in a team where management quite literally said they wanted to use a component library, because speed was of the essense and the team before me STILL went with Tailwind and completely ignored accessibility and wishes from management so yes... I'd say that's pretty cocky.

And the other reason why I say Tailwind and the people behind it are cocky, is because when you go to them with legitimate concerns about accessibility and clean code conventions, they don't respond to you about the topic, they just call you a hater or flat out say that you're a bad developer and you don't understand how awesome and god-like they are.

Thread Thread
natescode profile image
Nathan Hedglin

Ah that gives me a lot more insight thanks.

I'm not a Tailwind fanboy. I've used it. I like parts of it but like you said, often not used with something like MUI.

Nothing worse than being forced to use the wrong tool for the job. My work thinks CQRS is a panacea for backend design.

natescode profile image
Nathan Hedglin

You lost me. So don't use CSS classes at all? It's just a different approach.

Anyways. I apologize for the personal attack. I'm not a Tailwind fanboy but a lot of idiots (not you) hate on it that don't even understand that classes aren't anything like inline styles.

brense profile image
Rense Bakker

I mean along the lines of html element roles, like role=button for clickable elements for screen readers, proper aria labels for label/input relationships etc. There's a lot, more than I care to maintain myself, so having a UI framework that takes care of that for me is a big help.

Thread Thread
natescode profile image
Nathan Hedglin

Yeah Tailwind is a UI library not a framework.

Sounds like it just wasn't the right tool for what you were doing or there isn't components libraries that already do that.

Thanks! That definition helps.

Collapse
jancassio_10 profile image
Jan Cássio

Everyday a TW hater bringing JS oriented style as argument to replace CSS.

Collapse
brense profile image
Rense Bakker

No, I actually worked with it and discovered the many things Tailwind just cannot do, without very dirty workarounds. Also, its Tailwind that is trying to replace CSS, you're writing class names, not CSS.

Thread Thread
jancassio_10 profile image
Jan Cássio • Edited on

I actually worked with it and discovered the many things Tailwind just cannot do, without very dirty

Like what?

you're writing class names, not CSS

You understood CSS wrong. It's all about composing classes and reuse it. You should write more reusable CSS as you can to do not repeat it again and again. Tailwind provides a shortcut to skip the writing CSS for general purpose styles and avoid a lot of duplicates.

Collapse
natescode profile image
Nathan Hedglin

Great article!

Ignore the tailwind haters, their egos can't handle new approaches.

I HIGHLY recommend using atomic design with this stack. I use Next, React, Tailwind as well and with atomic, the structure is much cleaner. I'm able to build new pages extremely quickly because everything is broken into small components. This works really well with Tailwind too since you virtually never see CSS classes.

Collapse
pwnmonkey13 profile image
Giraudo Nicolas • Edited on

Super article !👏
Btw I hate haters and I love TW !
I worked with it on recents projects and I finally felt in love with !

That's said, I'm bootstrapping an NextJS app. TW and Prisma are already packed. And your article will help me deciding between graphql clients like Apollo and RPC ones like tRPC

Thanks mate

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Glad to know! 🙌 I hope you like the development experience 🤞

Collapse
wesleycoder profile image
Wésley Queiroz

Nice content.
It would be good to mention t3.gg so people can find more content on this stack. I imagine you got inspired by his work too.
Also in the Prerequisites section the only thing we need are Node/npm, because next is added during initialization and the other are added on the next steps.
Keep the good work.

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Thanks for the feedback! 👊 I've been using this stack for a few months now in personal projects, it's super fast to prototype an app, but it never crossed my mind to publish an article about it. And great point to mention Theo, I definitely recommend his content, not just for this stack but for his didactic approach, he's one of the few that isn't "tutorial-ish".

Collapse
firozansari profile image
Firoz Ansari

Great article! This is exact tech stack I was looking for.

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Glad to know! 👊

Collapse
laudaccurate profile image
Laud Gilbert

Great content here. The project is minimal but it teaches many vital techniques. Thanks Mendes

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Thanks for the feedback, this is exactly the intended result in my articles. Small examples, but with enough bases to do more 👊

Collapse
marcussa profile image
Marcus S. Abildskov

I'd suggest checking out Deepkit RPC.

Collapse
permanar profile image
Richie Permana

I'm not really sure since when I'm start hating Tailwind because I need to convert a Figma into pixel-perfect web but my designer never use "Tailwind approach" for it 🤷

Also, it's kinda hard to do scaling for higher resolution (because I obviously need to use vw unit)

Collapse
francisrod01 profile image
Francis Rodrigues

Which database do you recommend for Next.js?
They say that it doesn't work well with Firebase.