
Zero to Production: Deploying Next.js on Railway
Railway removed all the reasons I had to use Vercel. Here's a complete walkthrough including environment variables, Postgres, and custom domains.

React Server Components broke my mental model of React. Not because the API is complicated - it isn't - but because I kept trying to understand them as a variation of what I already knew.
Components render. Server components render on the server. Client components render on the client. Seemed simple, but I kept running into confusing errors and unexpected behavior until I stopped thinking about components and started thinking about render boundaries.
My first attempt: "Server components are like components but they run on the server and can't use hooks."
This led to questions like: "Why can't I pass a Server Component as a prop to a Client Component?" and "Why does making one component a Client Component seem to make everything under it a Client Component too?"
The component-centric model doesn't explain these things cleanly.
Here's the reframe: React renders in two passes - a server pass and a client pass. The question isn't "what type is this component" but "which pass does this code participate in?"
A "Client Component" isn't a component that only runs in the browser - it's a component that participates in the client pass (it still server-renders for the initial HTML). The 'use client' directive marks a boundary - everything below this point in the import tree joins the client bundle.
This explains the confusing behaviors:
Why can't you import a Server Component into a Client Component?
Because the client bundle can't contain server-only code (db queries, file system access, etc.). The 'use client' boundary is a one-way door - once you cross it, everything that gets imported must be safe to run in the browser.
But you CAN pass Server Components as props/children.
Because props are serialized as data - they cross the boundary as values, not as code. A Client Component receiving children from a Server Component isn't importing it; it's receiving the already-rendered output.
// This works - children cross as serialized output, not as code
export default function Layout({ children }) {
return <ClientShell>{children}</ClientShell> // ClientShell has 'use client'
}
// This doesn't work - imports cross the boundary as code
'use client'
import { ServerOnlyComponent } from './ServerOnlyComponent' // Error
Data fetching belongs at the render boundary, not deep in the tree.
In the old model, I'd often fetch data in a component and pass it down. With RSC, the pattern is: fetch at the highest server component that needs the data, pass it down as props.
'use client' should be as low in the tree as possible.
Each time you add 'use client', you're pulling that component and everything it imports into the client bundle. The goal is to keep interactivity as leaves, with the structural/data-fetching code staying on the server.
// Worse: entire page becomes client code
'use client'
export default function ProductPage({ id }) {
const [quantity, setQuantity] = useState(1)
const product = await db.products.findUnique({ where: { id } }) // Can't do this now
// ...
}
// Better: server fetches data, client handles interaction
export default async function ProductPage({ id }) {
const product = await db.products.findUnique({ where: { id } })
return <ProductView product={product} /> // ProductView has 'use client' for state
}
Once you have the render boundary model, Suspense makes more sense too. <Suspense> is how you tell React "this part of the tree can load asynchronously,show a fallback while it's pending."
In the App Router, every async Server Component is implicitly suspense-ready. You wrap sections in <Suspense> to control the loading UX at the granularity you want.
The render boundary model isn't the official React framing, but it's been the most useful way for me to reason about where to put components, when to add 'use client', and why the rules are what they are. Once it clicked, the rest of the App Router made sense.

Railway removed all the reasons I had to use Vercel. Here's a complete walkthrough including environment variables, Postgres, and custom domains.

End-to-end type safety without code generation. I'll show you how tRPC eliminates the API contract problem entirely and why it's become my go-to for full-stack TypeScript projects.

A deep dive into how curiosity, client-side analysis, and cloud computing helped me climb to the top of Toptal's JS leaderboard.