<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nathan</title>
    <description>The latest articles on DEV Community by Nathan (@nathan-brodin).</description>
    <link>https://dev.to/nathan-brodin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3806287%2Fccb3d6e1-eb01-4eff-8594-cc7e3a837f9e.jpg</url>
      <title>DEV Community: Nathan</title>
      <link>https://dev.to/nathan-brodin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nathan-brodin"/>
    <language>en</language>
    <item>
      <title>Between Tradition and Modernity: Building a Full Stack App with Django and React</title>
      <dc:creator>Nathan</dc:creator>
      <pubDate>Mon, 13 Apr 2026 08:15:14 +0000</pubDate>
      <link>https://dev.to/nathan-brodin/between-tradition-and-modernity-building-a-full-stack-app-with-django-and-react-4pma</link>
      <guid>https://dev.to/nathan-brodin/between-tradition-and-modernity-building-a-full-stack-app-with-django-and-react-4pma</guid>
      <description>&lt;p&gt;If I tell you I made a full-stack app with &lt;a href="https://react.dev/blog/2024/12/05/react-19" rel="noopener noreferrer"&gt;React 19&lt;/a&gt; (+ &lt;a href="https://react.dev/learn/react-compiler" rel="noopener noreferrer"&gt;Compiler&lt;/a&gt;), &lt;a href="https://tanstack.com/router/latest" rel="noopener noreferrer"&gt;Tanstack Router&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;tailwindcss&lt;/a&gt;, &lt;a href="https://base-ui.com/" rel="noopener noreferrer"&gt;Base UI&lt;/a&gt;, and &lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt;, you would probably expect a &lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;Hono backend&lt;/a&gt; or Tanstack Start &lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/server-functions" rel="noopener noreferrer"&gt;Server functions&lt;/a&gt; with &lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle&lt;/a&gt;, or at least some cutting-edge TS solution. Well, I've built a &lt;a href="https://www.django-rest-framework.org/" rel="noopener noreferrer"&gt;Django&lt;/a&gt; backend, and it works pretty well!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraints vs. The Freedom
&lt;/h2&gt;

&lt;p&gt;When starting a new project, you always face constraints. Your job as the person designing the software architecture is to find the most elegant way to build around them.&lt;/p&gt;

&lt;p&gt;Here are the constraints I was handed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django for the backend (to match the team's existing legacy projects).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.keycloak.org/" rel="noopener noreferrer"&gt;Keycloak&lt;/a&gt; for authentication.&lt;/li&gt;
&lt;li&gt;Dockerizing the entire stack for self-hosted deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The freedom? I got to decide absolutely everything else.&lt;/p&gt;

&lt;p&gt;If you’ve read my previous blog posts, you know I’ve fallen in love with the Tanstack ecosystem. I went with Tanstack Router, Query, Form, Table, and Pacer. Notice that I &lt;em&gt;didn't&lt;/em&gt; go with Tanstack Start. Given the actual goals of this app, I couldn't justify the SSR overhead, and I absolutely did not want to spend a single second fixing hydration issues (I still have nightmares about them).&lt;/p&gt;

&lt;p&gt;For the UI, I finally got to use TailwindCSS and shadcn/ui at work, freeing myself from plain CSS and the horrors of &lt;code&gt;styled-components&lt;/code&gt;. I really love the pattern of creating headless, reusable components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PageHeader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTMLAttributes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;cn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grid auto-rows-min items-start gap-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;data-slot&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"page-header"&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Yes, it kind of looks like styled-components in a way. Maybe time is a flat circle?)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A quick shoutout to two other bangers in the frontend stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://inlang.com/m/gerre34r/library-inlang-paraglideJs" rel="noopener noreferrer"&gt;Paraglide JS&lt;/a&gt;:&lt;/strong&gt; After fighting with &lt;code&gt;react-i18next&lt;/code&gt; (lack of type safety, fetching all keys client-side), I switched to Paraglide JS on &lt;a href="https://tanstack.com/router/latest/docs/guide/internationalization-i18n#tanstack-router--paraglide-client-only" rel="noopener noreferrer"&gt;Tanstack's recommendation&lt;/a&gt;. Zero downsides so far.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://knip.dev/" rel="noopener noreferrer"&gt;Knip&lt;/a&gt;:&lt;/strong&gt; Analyzes your codebase for unused files, exports, and dependencies. Even with the strictest ESLint/Prettier setup, you’ll have dead code. Knip is a godsend for cleanup.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Bridging the Gap: The Auth Weirdness
&lt;/h2&gt;

&lt;p&gt;Let's talk about the &lt;em&gt;weird&lt;/em&gt; authentication layer in the app, that I am not a big fan of.&lt;/p&gt;

&lt;p&gt;The client authenticates with Keycloak. This means I need to check auth on the frontend (using &lt;code&gt;react-oidc-context&lt;/code&gt; and &lt;code&gt;oidc-client-ts&lt;/code&gt;, which have pretty bad documentation) and &lt;a href="https://tanstack.com/router/latest/docs/how-to/setup-authentication#2-configure-router" rel="noopener noreferrer"&gt;store the auth context&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I then pass the key to the backend during API calls, where Django verifies it using &lt;code&gt;jwt.decode&lt;/code&gt; against the public key. It doesn't sound that bad, except that Django has its own pre-built auth system with user tables, and Keycloak isn't designed to store app-specific user metadata. So, I had to build a weird, performant sync layer between the two (e.g., if a Keycloak email changes, reflecting it locally in the Postgres DB). It’s clunky, but it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type Safety is still possible
&lt;/h2&gt;

&lt;p&gt;I love type safety. Coming from TypeScript, and having played with C and Dart in school, dynamic typing gives me hives.&lt;/p&gt;

&lt;p&gt;Out of the box, Python can feel a bit like the Wild West compared to a strict TypeScript setup. With standard dependencies often living in a simple &lt;code&gt;.txt&lt;/code&gt; file and optional linting, it gives you a lot of freedom. But that freedom means you have to actively put in the work to enforce a strong Developer Experience, otherwise code quality can slip quickly.&lt;/p&gt;

&lt;p&gt;However, having a Python backend and a TypeScript frontend doesn't mean you have to sacrifice end-to-end type safety. Here is how I forced the two to play nice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django properly defines the models with strict types and comments.&lt;/li&gt;
&lt;li&gt;Backend views have full documentation on response types using those models.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://drf-spectacular.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;drf-spectacular&lt;/a&gt; generates the OpenAPI specs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://orval.dev/" rel="noopener noreferrer"&gt;Orval&lt;/a&gt; generates TS types and query hooks from those specs.&lt;/li&gt;
&lt;li&gt;The frontend consumes the &lt;a href="https://tanstack.com/query/latest" rel="noopener noreferrer"&gt;Tanstack Query Hooks&lt;/a&gt; to fetch data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And just like that... End-to-end type safety. You know exactly what the endpoint needs, and exactly what it's going to return. When you make a change in a model, you get the feedback all the way to your frontend component.&lt;/p&gt;




&lt;h2&gt;
  
  
  DX: Making the Containers Bearable
&lt;/h2&gt;

&lt;p&gt;After the users, my top priority is the developers (and code quality). Because of the strict constraints, I got to tear my hair out properly learning Docker. Setting up multiple services, ensuring they communicate, and managing deployment across two environments in a server full of existing apps was a massive headache.&lt;/p&gt;

&lt;p&gt;But once it works, Docker is magic. Starting the project takes one command. You get fully reproducible environments between local dev and production. So now you can use the "But it works on my machine" excuse more confidently.&lt;/p&gt;

&lt;p&gt;I also spent some time creating &lt;a href="https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html" rel="noopener noreferrer"&gt;Make commands&lt;/a&gt;. These Docker commands are quite long and spending 5 minutes going up in the terminal history trying to find the specific command to run the tests can be quite annoying. So I wrote a &lt;code&gt;Makefile&lt;/code&gt;. Now, a simple &lt;code&gt;make codegen&lt;/code&gt; spins up the OpenAPI specs and frontend types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;COMPOSE_FILE&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; docker-compose.dev.yml
&lt;span class="nv"&gt;ENV_FILE&lt;/span&gt;     &lt;span class="o"&gt;:=&lt;/span&gt; .env.local
&lt;span class="nv"&gt;COMPOSE&lt;/span&gt;      &lt;span class="o"&gt;:=&lt;/span&gt; docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;COMPOSE_FILE&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--env-file&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;ENV_FILE&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;codegen&lt;/span&gt;
&lt;span class="nl"&gt;codegen&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;schema types &lt;/span&gt;&lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; Generate both Schema and Types&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;schema&lt;/span&gt;
&lt;span class="nl"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; Generate Open API schema from Backend&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;COMPOSE&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;exec &lt;/span&gt;backend python manage.py spectacular &lt;span class="nt"&gt;--file&lt;/span&gt; openapi.yml &lt;span class="nt"&gt;--validate&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;types&lt;/span&gt;
&lt;span class="nl"&gt;types&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; Generate TypeScript types from Open API schema&lt;/span&gt;
    &lt;span class="p"&gt;$(&lt;/span&gt;COMPOSE&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;exec &lt;/span&gt;frontend pnpm run generate-types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also built a strong CI pipeline. It handles backend linting and formatting, 800+ Django tests, migration checks, OpenAPI schema validation, Frontend Schema Types validation, frontend type checking, frontend build, and finally Playwright tests.&lt;/p&gt;

&lt;p&gt;It sounds heavy, but it only takes ~7 minutes if &lt;em&gt;all&lt;/em&gt; steps run, thanks to aggressive caching, sharding, and parallel jobs. If I only touch backend code, the pipeline finishes in 2 minutes. Stop the pipeline early, run only what changed. It’s worth the initial setup time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boring isn't bad
&lt;/h2&gt;

&lt;p&gt;Django is not the most exciting tech, but it’s great for a CRUD app exposing APIs to Postgres. Yes, I still have to handle some complexity: RBAC, Redis caching, querying a Clickhouse DB with raw SQL, and WebSockets for live notifications, but I’m not building a crazy app for millions of users.&lt;/p&gt;

&lt;p&gt;Django is simple, predictable, and LLMs understand it perfectly. Need a cache layer? Two lines of code. It’s fast enough that running 800+ tests (including DB writes) takes 10 seconds.&lt;br&gt;
I still have some issues with it, like if there is an internal server error, an endpoint will return some html by default. So you need a custom middleware to formalize all kinds of errors. And of course, it has to be in Python. But overall: it just works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Silo Problem: Designing APIs in a Vacuum
&lt;/h2&gt;

&lt;p&gt;Now, for a bit of a reality check regarding backend development.&lt;/p&gt;

&lt;p&gt;In theory, backend engineers handle incredibly important tasks: complex business logic, rock-solid security, scaling, and database optimizations. This should put to shame frontend engineers like me who spend 2 hours changing the color of a button.&lt;/p&gt;

&lt;p&gt;But in my early years of corporate experience, my reality has been quite different. I have only encountered codebases where basic software engineering practices, like mandatory PR reviews, automated testing, or even basic linting, just weren't part of the culture. When teams don't put effort into those foundations, you quickly end up with messy codebases, weak RBAC, major security oversights, and poor performance. More importantly, I’ve encountered the "Silo Problem."&lt;/p&gt;

&lt;p&gt;Here’s a story from a past job. We were building an AI Chat app. I was the solo frontend dev, working with a UI/UX designer, a handful of backend devs, and some AI engineers.&lt;/p&gt;

&lt;p&gt;The backend team held their planning meetings without bringing in the frontend or the UI designer. Because of that disconnect, the database relations and endpoints were designed without the actual client application in mind. The result made zero sense for the UI: they added data models for UI themes using variables that completely clashed with the design system, and created endpoints structured in a way that required six separate workarounds on the client just to render a basic view. I ended up rewriting the frontend logic three separate times to keep up with OpenAPI specs that were handed down after the fact (which, naturally, rarely matched the live responses). It was an incredibly frustrating but valuable lesson in why API design has to be collaborative.&lt;/p&gt;

&lt;p&gt;If you are a backend developer adding a new endpoint, you aren't doing it for fun. You are doing it because the user interface needs that data. Designing APIs without consulting the client-side needs is like building a steering wheel without checking what kind of car it's going into.&lt;/p&gt;

&lt;p&gt;Thankfully, on my current project, I am the frontend, backend, and DevOps engineer. Everything communicates nicely, because I actually talk to myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Corporate Reality
&lt;/h2&gt;

&lt;p&gt;I started this new grad job in mid-2025 at a small non-tech company. I turned down a crazy offer at a massive corporation specifically because I wanted the freedom to build good products and actually care about software architecture.&lt;br&gt;
And I did get that freedom. I spent hours refining small details, optimizing DX, and over-engineering the type safety.&lt;br&gt;
But as the months go by, a grounding realization has set in:&lt;/p&gt;

&lt;p&gt;This app will likely never have more than 8 concurrent users.&lt;br&gt;
Looking at the history of internal projects, business priorities pivot fast, and apps are sometimes replaced after a year or two.&lt;/p&gt;

&lt;p&gt;It's a humbling thought. But I've come to see it less as a reason not to care, and more as a reminder to calibrate. Good architecture isn't just about scale, but also it's maintainability, handoff-ability, and not leaving a mess for the next person. Those things matter even if the app never sees a traffic spike.&lt;/p&gt;

&lt;p&gt;But in life, you make compromises. I’m getting paid to learn, I built an architecture I’m proud of, and at the end of the day, I get to log off and live in one of the &lt;a href="https://www.google.com/search?q=troms%C3%B8+aurora&amp;amp;tbm=isch" rel="noopener noreferrer"&gt;most beautiful places in the world&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>django</category>
      <category>react</category>
      <category>full</category>
    </item>
    <item>
      <title>"You Wouldn’t Steal a DIV": How I Built My Portfolio</title>
      <dc:creator>Nathan</dc:creator>
      <pubDate>Thu, 12 Mar 2026 14:35:18 +0000</pubDate>
      <link>https://dev.to/nathan-brodin/you-wouldnt-steal-a-div-how-i-built-my-portfolio-2a25</link>
      <guid>https://dev.to/nathan-brodin/you-wouldnt-steal-a-div-how-i-built-my-portfolio-2a25</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A story about how I built my portfolio and what went through my mind while building it&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello, World! Here's my first ever blog.&lt;/p&gt;

&lt;p&gt;I wanted to talk a bit about my &lt;a href="https://brodin.dev" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;, how I built it, why I made some of these architectural and design decisions, and why I shamelessly 'borrowed' my way to designs I love.&lt;/p&gt;

&lt;h2&gt;
  
  
  1 Stack, 2 Stack, Tech Stack, 4
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;(If you didn't read that header &lt;a href="https://youtu.be/F2hiFbuQ-Qw?si=h6omylv9pJwJIVgp&amp;amp;t=93" rel="noopener noreferrer"&gt;in Slim Shady's voice&lt;/a&gt;, I don't know what to tell you.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First of all, let's talk tech stack. I used to have all of my personal projects (including my previous portfolio) built with &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;. These were small side projects, without complicated logic, and with simple needs. Yet, I would always run into Next.js edge cases. Turbopack was failing my dev builds because it wouldn't play nice with Contentlayer, HMR was taking actual seconds to refresh, and production builds were taking 50 seconds for projects that barely scaled. When I got to choose a stack for a new project at my job, I couldn't justify the Next.js overhead for production apps, considering the friction I had with small personal projects.&lt;/p&gt;

&lt;p&gt;At that time, everyone was talking about &lt;a href="https://tanstack.com/start/latest" rel="noopener noreferrer"&gt;Tanstack Start&lt;/a&gt;: the "DX God." But even their starter app had linting and TS issues, so I wasn't fully impressed. I stuck with only Tanstack Router for that project, and as weeks passed by working with the entire Tanstack Ecosystem (except Start), I was really enjoying it. The documentation is well written and extensive (even too much sometimes), DX actually made sense, and I could find all of the features I liked about Next.js (file base routing for example) while still having great performance (15s builds).&lt;/p&gt;

&lt;p&gt;So this led me to give a second chance to Tanstack Start, and I built this portfolio with it! I still love Vercel's UX, and wander around their dashboard to see how I should implement my stuff, but I think I'll leave Next.js behind for now. And personal projects are made to experiment with stuff. So I just want to try out different tech and see what I like or not, purely on feelings.&lt;/p&gt;

&lt;p&gt;Then for the component library, I was really into &lt;a href="https://coss.com/ui" rel="noopener noreferrer"&gt;coss ui&lt;/a&gt;. I stumbled upon it randomly one day and loved it so much. My &lt;a href="https://chat.brodin.dev" rel="noopener noreferrer"&gt;Nathan's AI&lt;/a&gt; project already had a UI heavily inspired by &lt;a href="https://cal.com/" rel="noopener noreferrer"&gt;cal.com&lt;/a&gt;: send button, message suggestions... So when I saw they had a &lt;a href="https://ui.shadcn.com/" rel="noopener noreferrer"&gt;shadcn/ui&lt;/a&gt; library with that kind of style, it was perfect for what I needed.&lt;/p&gt;

&lt;p&gt;I can also consider myself a proud Open Source Contributor after getting &lt;strong&gt;two PRs&lt;/strong&gt; merged into &lt;a href="https://coss.com/ui" rel="noopener noreferrer"&gt;coss ui&lt;/a&gt; (a 2-line diff and I'm not even kidding, but quality over quantity, I guess...?).&lt;/p&gt;

&lt;h2&gt;
  
  
  Stealing...I Mean, Getting Inspired By Chanhdai and Zed
&lt;/h2&gt;

&lt;p&gt;Now I'm not going to lie and say I designed and coded everything myself. I took large parts of the code and page layout from the open-source portfolio by &lt;a href="https://chanhdai.com" rel="noopener noreferrer"&gt;Chanhdai&lt;/a&gt; and design inspiration from &lt;a href="https://zed.dev" rel="noopener noreferrer"&gt;zed.dev&lt;/a&gt;, which I had previously attempted in unfinished side projects (&lt;a href="https://trends.brodin.dev" rel="noopener noreferrer"&gt;https://trends.brodin.dev&lt;/a&gt; and &lt;a href="https://ui.brodin.dev" rel="noopener noreferrer"&gt;https://ui.brodin.dev&lt;/a&gt; if they're still available).&lt;/p&gt;

&lt;p&gt;I still went a different way than Chanhdai for the code implementation. For example, I chose to store my content in markdown files using &lt;a href="https://www.content-collections.dev/" rel="noopener noreferrer"&gt;Content Collections&lt;/a&gt;. I did this specifically so I could easily serve my portfolio's content in plain text, perfect for LLM consumption, which ties right back into the needs of my &lt;a href="https://chat.brodin.dev" rel="noopener noreferrer"&gt;Nathan's AI&lt;/a&gt; project.&lt;br&gt;
I also used Base-ui (because of coss ui) for components, and generally went with a different style. So this was not just a simple copy/paste or a fork, there was still a lot of work involved.&lt;/p&gt;

&lt;p&gt;For Zed, I took the &lt;a href="https://zed.dev/attributions#fonts" rel="noopener noreferrer"&gt;same fonts&lt;/a&gt; for the headings and text, and the grid layout with the "diamonds" at the intersections. It really adds personality to my portfolio (not &lt;em&gt;my&lt;/em&gt; personality since I stole it, but &lt;strong&gt;some&lt;/strong&gt; personality).&lt;/p&gt;

&lt;p&gt;Also, by a great coincidence, Chanhdai had very basic sound design, like a sound when changing the theme, and I wanted to push things further by having different sounds around the entire app. I was looking around on the web for good sound libraries and all, but couldn't find anything interesting. Then the next day, I saw a tweet about the upcoming release of &lt;a href="https://www.soundcn.xyz/" rel="noopener noreferrer"&gt;Soundcn&lt;/a&gt;, which was &lt;strong&gt;exactly&lt;/strong&gt; what I was looking for. So now you get a bunch of sounds when clicking on stuff in my portfolio. It's a nice touch!&lt;/p&gt;

&lt;h2&gt;
  
  
  From good to WHOA
&lt;/h2&gt;

&lt;p&gt;Lastly, the thing that elevated my portfolio from a good portfolio to a &lt;strong&gt;Whoa&lt;/strong&gt; portfolio are the dither shaders (from &lt;a href="https://shaders.paper.design/dithering" rel="noopener noreferrer"&gt;Paper Shaders&lt;/a&gt;) that I have for page headings and section dividers. I had dither shaders in mind since the first day I saw them on &lt;a href="https://reactbits.dev/backgrounds/dither" rel="noopener noreferrer"&gt;React Bits&lt;/a&gt;, but couldn't find a good way to integrate them. So it ended up being a crossover between inspiration from the heading of zed.dev (open the dev console and see &lt;code&gt;data-paper-shader&lt;/code&gt; on the headings) and &lt;a href="https://www.fumadocs.dev/" rel="noopener noreferrer"&gt;Fumadocs&lt;/a&gt;, which had them as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  To wrap things up
&lt;/h2&gt;

&lt;p&gt;So in conclusion, I would say this portfolio is a cross between Chanhdai and Zed, with a better stack.&lt;/p&gt;

&lt;p&gt;Go ahead, click around to hear the sound integration (it gets annoying after a while, be careful), check out the dither shaders on the headings, and &lt;a href="https://github.com/NathanBrodin/Portfolio" rel="noopener noreferrer"&gt;check out the source code here&lt;/a&gt; if you want to steal from me like I did from everyone (but at least leave a star on the repo).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blog Title reference: &lt;a href="https://www.youtube.com/watch?v=P-pYiWGSN8w" rel="noopener noreferrer"&gt;This classic: "You Wouldn't Steal a Car"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;My Portfolio: &lt;a href="https://brodin.dev" rel="noopener noreferrer"&gt;brodin.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Initially posted on &lt;a href="https://brodin.dev/blog/how-i-built-my-portfolio" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>portfolio</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
