My Stack
Aleksa MiticHere's where I'm currently most productive:
1. Framework (Next.js and React)
I’ve been working with Next.js since 2022 (and React since 2021) so I'm most productive here. I start all my projects with TypeScript.
For data fetching, I use newer React patterns:
Majority: Server Components
Most of the time, I fetch data in a Server Component (like a page
or layout
). Occasionally, I'll still use an external library for client-fetches like react-query
from tanstack
.
export default async function Page() {
let data = await fetch('https://api.vercel.app/blog')
let posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
For forms, I use react-hook-form and zod to validate objects.
2. Styling (Tailwind CSS and shadcn/ui)
Building flexible, accessible components is difficult. You either use (and extend) a component library, or build your own. This is why I now use shadcn/ui.
It provides well-designed and extensible components, built on top of accessible, unstyled primitives. This includes basics like buttons and inputs, but also icons, charts, and even custom themes.
Components are styled with Tailwind CSS—the most AI-friendly CSS library. Why? You can easily colocate your styles with your markup. This makes generating and editing code with AI tools much easier.
If I need to build something from scratch, that needs to be reusable, I use CVA to setup the component. For merging styles, I combine cslx with tailwind merge. Like this:
CVA example:
const button = cva({
base: 'rounded-md px-4 py-2',
variants: {
variant: {
primary: 'bg-blue-500 text-white',
secondary: 'bg-gray-500 text-white',
},
},
});
CSLX example:
import { clsx, ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
export default cn;
Why Tailwind?
Tailwind uses a compiler to generate only the classes used. So while the utility CSS framework contains many possible class names, only the classes used will be included in the single, compiled CSS file.
Assuming you only write Tailwind code, your bundle will never be larger than the total set of used Tailwind classes. It's extremely unlikely you would use them all. This means you have a fixed upper bound on the size of the generated CSS file, which is then minified, compressed, and cached for the best performance.
You don't have to only write Tailwind styles. Tailwind classes are just utilities that adhere to a design system and work alongside normal CSS. You can mix and match Tailwind with CSS Modules, for example.
3. Database (Postgres and Prisma)
Postgres is my go-to database. Prisma makes working with Postgres easy, type-safe, and fun. I can view and modify my data using Prisma Studio and manage migrations with the Prisma CLI.
The best part? Prisma works perfectly with TypeScript, providing end-to-end type safety from your database to your application code.
4. AI (Cursor, Upstash, Braintrust)
Cursor helps me edit, refactor, and debug code.
Upstash is my go-to for caching. And also if need to integrate Vector database. Also recenty has very good support for RAG chat's and other LLM tools.
Braintrust is my go-to for AI code reviews and for doing Evals.
5. Coding Patterns
- Components folder is my go-to for building components. Contains atomic design: Atomic Design
- Server stuff keeping in (server) folder
- Larger files in organisms > many small components all over the place
- Fetching data in a Server Component (like a
page
orlayout
) - Copy/paste is better than the wrong abstraction
- You might not want SVGs in your JSX (explore sprites)