<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Dorian McClintock</title>
        <link>https://dorianmcclintock.com</link>
        <description>Notes on building audio plugins and music tools, producing music, and web development.</description>
        <lastBuildDate>Mon, 19 Oct 2026 06:26:28 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Dorian McClintock</title>
            <url>https://dorianmcclintock.com/favicon.ico</url>
            <link>https://dorianmcclintock.com</link>
        </image>
        <copyright>All rights reserved 2026</copyright>
        <item>
            <title><![CDATA[Building DMC60, a 12-bit drum machine where the grit is the point]]></title>
            <link>https://dorianmcclintock.com/articles/building-dmc60-a-12-bit-drum-machine</link>
            <guid>https://dorianmcclintock.com/articles/building-dmc60-a-12-bit-drum-machine</guid>
            <pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>DMC60 is the project I've spent more time on than anything else this year. It's a drum machine and sampler built with JUCE, and it's designed around one idea: the "flaws" of the classic 12-bit samplers are what make drums hit the way they do on so many records.</p>
<p>So DMC60 doesn't try to be clean. Everything runs through a <strong>26.04 kHz internal rate</strong>, samples are <strong>quantized to 12 bits</strong>, and there's an <strong>SSM2044-style filter</strong> on the voice. The aliasing you get from that low rate is deliberate. In the README I put it this way: those are <em>features</em>, not bugs.</p>
<h2>Why 12 bits and 26.04 kHz</h2>
<p>Modern plugins give you 32-bit float at whatever rate your session runs at, which is great for transparency. But play a kick through a 12-bit converter at 26 kHz and you get something clean audio doesn't give you: a crunchy top end, folded-back harmonics, and a low end that feels denser than it measures.</p>
<p>The core of the effect is simple. Here's a sketch of the quantization step. The clamp matters: a signed 12-bit converter tops out at +2047, so a full-scale peak clips slightly instead of landing on a level the hardware never had.</p>
<pre class="language-cpp"><code class="language-cpp"><span class="token comment">// Simplified: snap a float sample in [-1, 1] to a signed 12-bit grid</span>
<span class="token keyword">inline</span> <span class="token keyword">float</span> <span class="token function">quantize12</span> <span class="token punctuation">(</span><span class="token keyword">float</span> x<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">constexpr</span> <span class="token keyword">float</span> scale <span class="token operator">=</span> <span class="token number">2048.0f</span><span class="token punctuation">;</span> <span class="token comment">// 2^12 levels, split across both polarities</span>
    <span class="token keyword">const</span> <span class="token keyword">float</span> code <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token function">clamp</span> <span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span><span class="token function">round</span> <span class="token punctuation">(</span>x <span class="token operator">*</span> scale<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">2048.0f</span><span class="token punctuation">,</span> <span class="token number">2047.0f</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> code <span class="token operator">/</span> scale<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
<p>The harder part is everything around it. The sample-rate conversion has to alias the way the old hardware did instead of filtering it away, and the filter has to respond the way the analog chip did. That's where most of my time went.</p>
<h2>One codebase, four platforms</h2>
<p>DMC60 builds from a single CMake project into:</p>
<table><thead><tr><th>Format</th><th style="text-align:center">macOS</th><th style="text-align:center">Windows</th><th style="text-align:center">Linux</th><th style="text-align:center">iPadOS</th></tr></thead><tbody><tr><td>AU</td><td style="text-align:center">✅</td><td style="text-align:center">—</td><td style="text-align:center">—</td><td style="text-align:center">—</td></tr><tr><td>VST3</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">—</td></tr><tr><td>Standalone</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr></tbody></table>
<p>JUCE does most of the cross-platform work, but the iPad version took real design effort. A drum machine is something you want to <em>touch</em>, so the iPad app got its own layout, not a shrunk-down desktop window:</p>
<ul>
<li>Native point sizes, with <strong>44-point</strong> minimum button targets and <strong>48×44-point</strong> fader handles</li>
<li>Pad controls and the session inspector start closed, so the 4×4 pad grid gets the screen</li>
<li>Background audio, and live sampling from the microphone</li>
<li>Projects and programs live in the app's Documents folder, so they show up in the Files app</li>
</ul>
<p>I leaned on <a href="https://developer.apple.com/design/human-interface-guidelines/buttons">Apple's button guidance</a> along with <a href="https://lawsofux.com/fittss-law/">Fitts's Law</a> and <a href="https://lawsofux.com/hicks-law/">Hick's Law</a> to size and simplify the controls. Bigger targets that are closer to your thumb, and fewer choices on screen at once.</p>
<p>I also added a desktop preview mode. Launching the standalone with <code>DMC60_IPAD_LAYOUT=1</code> renders the iPad layout on the Mac, and a <code>touch_layout_smoke</code> test renders the real editor at iPad size to check pad and toolbar dimensions without touching audio hardware.</p>
<h2>A pre-commit hook that builds everything</h2>
<p>One habit that paid off: the repo's pre-commit hook runs a <strong>full Release build</strong> before every commit. It's slower than a lint check, but a C++ plugin that doesn't build is worthless, and this way the main branch always does.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> run prepare        <span class="token comment"># point git at .githooks</span>
./scripts/build-app.sh <span class="token comment"># the same build the hook runs</span>
</code></pre>
<h2>What's next</h2>
<p>DMC60 has its own program format (<code>.dmcpgm</code>), and building kits for it by hand got old fast. That's what led to my next tool, <a href="/articles/turning-sample-folders-into-mpc-kits-with-rust">DorDSP Kit Pro</a>, which turns folders of samples into ready-to-play kits for DMC60, the MPC, and Ableton.</p>]]></content:encoded>
            <author>dorian.mcclintock@gmail.com (Dorian McClintock)</author>
        </item>
        <item>
            <title><![CDATA[Building my first audio plugin with JUCE]]></title>
            <link>https://dorianmcclintock.com/articles/building-my-first-audio-plugin-with-juce</link>
            <guid>https://dorianmcclintock.com/articles/building-my-first-audio-plugin-with-juce</guid>
            <pubDate>Thu, 05 Sep 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>After years of tweaking knobs on other people's plugins, I finally decided to build my own. The idea had been bouncing around in my head for a while: a simple tube saturation plugin that actually sounded warm without being muddy.</p>
<h2>Why JUCE?</h2>
<p>If you're getting into audio plugin development, JUCE is basically the industry standard. It handles the cross-platform plumbing (VST3, AU, and AAX with Avid's SDK) so you can focus on the actual DSP. The learning curve is real, but the documentation is solid and there's a great community.</p>
<pre class="language-cpp"><code class="language-cpp"><span class="token keyword">void</span> <span class="token class-name">WarmTubeProcessor</span><span class="token double-colon punctuation">::</span><span class="token function">processBlock</span><span class="token punctuation">(</span>juce<span class="token double-colon punctuation">::</span>AudioBuffer<span class="token operator">&lt;</span><span class="token keyword">float</span><span class="token operator">&gt;</span><span class="token operator">&amp;</span> buffer<span class="token punctuation">,</span> juce<span class="token double-colon punctuation">::</span>MidiBuffer<span class="token operator">&amp;</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    juce<span class="token double-colon punctuation">::</span>ScopedNoDenormals noDenormals<span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> channel <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> channel <span class="token operator">&lt;</span> buffer<span class="token punctuation">.</span><span class="token function">getNumChannels</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>channel<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">auto</span><span class="token operator">*</span> channelData <span class="token operator">=</span> buffer<span class="token punctuation">.</span><span class="token function">getWritePointer</span><span class="token punctuation">(</span>channel<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> sample <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> sample <span class="token operator">&lt;</span> buffer<span class="token punctuation">.</span><span class="token function">getNumSamples</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>sample<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token comment">// Soft clipping with tube-style saturation</span>
            <span class="token keyword">float</span> x <span class="token operator">=</span> channelData<span class="token punctuation">[</span>sample<span class="token punctuation">]</span> <span class="token operator">*</span> driveAmount<span class="token punctuation">;</span>
            channelData<span class="token punctuation">[</span>sample<span class="token punctuation">]</span> <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token function">tanh</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token operator">*</span> outputGain<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>The basic idea behind tube saturation is soft clipping. When you push a tube amp, it doesn't cut the signal off harshly. It rounds it off smoothly, adding harmonics that our ears hear as "warmth."</p>
<p>There's a catch, though. <code>tanh()</code> is symmetric, so it only adds <em>odd</em> harmonics, which lean toward grit more than warmth. Real tubes are asymmetric, and that asymmetry is where the even harmonics come from. Adding a small DC bias before the curve (and removing it afterward) gets you a lot closer.</p>
<h2>The DSP rabbit hole</h2>
<p>What started as a simple <code>tanh()</code> waveshaper quickly became an obsession with modeling how vacuum tubes actually behave. There's a reason good saturation plugins cost what they do: the math gets complicated fast.</p>
<p>Key things I learned:</p>
<ol>
<li><strong>Oversampling is essential.</strong> Any nonlinearity creates new harmonics, and the ones above Nyquist fold back down as aliasing. JUCE's <code>dsp::Oversampling</code> class makes this easy.</li>
<li><strong>Smooth your parameters.</strong> Jumping straight to a new drive value mid-buffer causes zipper noise. <code>SmoothedValue</code> fixes it.</li>
<li><strong>Filters matter.</strong> Pre- and post-filtering shape the character dramatically.</li>
<li><strong>UI is half the battle.</strong> A good-looking plugin makes you want to use it.</li>
</ol>
<h2>What's next</h2>
<p>I'm currently working on a proper EQ section and a bias control, so you can dial in how asymmetric (and how "warm") the curve gets. The goal is to release it as a free plugin once it's polished enough.</p>
<p>If you're thinking about building your own plugins, I'd say go for it. The combination of music and code is incredibly satisfying, and there's nothing quite like hearing your own DSP in a mix.</p>]]></content:encoded>
            <author>dorian.mcclintock@gmail.com (Dorian McClintock)</author>
        </item>
        <item>
            <title><![CDATA[Setting up a home studio that actually sounds good]]></title>
            <link>https://dorianmcclintock.com/articles/setting-up-a-home-studio-on-a-budget</link>
            <guid>https://dorianmcclintock.com/articles/setting-up-a-home-studio-on-a-budget</guid>
            <pubDate>Mon, 15 Jul 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>One of the most common questions I get is about my studio setup. People assume you need tens of thousands of dollars to make music that sounds professional. You don't.</p>
<h2>Start with the room</h2>
<p>Before you spend money on gear, think about your space. I started out in a spare bedroom in my Bay Area apartment, which came with all the acoustic problems you'd expect: parallel walls, hardwood floors, and neighbors who don't love 808s at midnight.</p>
<p>The fixes don't have to be expensive:</p>
<ul>
<li><strong>Bass traps in the corners.</strong> This made the biggest difference. Small rooms pile up low end in the corners, and that's what makes mixes sound boomy in one room and thin everywhere else.</li>
<li><strong>Absorption panels at the first reflection points.</strong> Have a friend slide a mirror along the side walls while you sit in the mix position. Anywhere you can see a speaker in the mirror gets a panel. DIY rockwool panels work great.</li>
<li><strong>A thick rug.</strong> Seriously, it helps more than you'd think.</li>
<li><strong>Speaker placement.</strong> Put your monitors and your head in an equilateral triangle, and keep the monitors off the wall behind them. It's free and it matters.</li>
</ul>
<h2>The gear that matters</h2>
<p>Here's my current signal chain for tracking and mixing:</p>
<pre><code>Guitar/Keys → Apollo Twin X → Yamaha HS8s
                    ↓
            MacBook Pro M1 Max
                    ↓
              Ableton Live
</code></pre>
<p>The Apollo Twin was my biggest investment, but the built-in UAD processing and near-zero latency monitoring made it worth it. I can track through an LA-2A emulation without any delay I can hear.</p>
<h2>Plugins over hardware</h2>
<p>Hot take: for most home producers, plugins are a better investment than hardware. My FabFilter bundle and Soundtoys collection get used on every session. A clean, flexible hardware compressor in the same league as Pro-C 2 costs many times more, and you only get one instance of it.</p>
<h2>The workflow advantage</h2>
<p>The real benefit of a home setup is iteration speed. I can wake up with an idea, be tracking within 5 minutes, and have a rough mix by lunch. No booking studio time, no commute, no pressure.</p>
<p>My French bulldogs make excellent A&amp;R. If they fall asleep during playback, I know the track needs more energy.</p>
<h2>Keep it simple</h2>
<p>The best studio is the one that gets out of your way. I've seen people with $50k setups who never finish anything because they're too busy tweaking gear. Start small, learn your tools deeply, and upgrade only when something is genuinely holding you back. My setup has grown a lot since I wrote this (the current list is on my <a href="/uses">uses page</a>), but every upgrade came from hitting a real limit, not from wanting new gear.</p>]]></content:encoded>
            <author>dorian.mcclintock@gmail.com (Dorian McClintock)</author>
        </item>
        <item>
            <title><![CDATA[Turning folders of samples into MPC kits with Rust]]></title>
            <link>https://dorianmcclintock.com/articles/turning-sample-folders-into-mpc-kits-with-rust</link>
            <guid>https://dorianmcclintock.com/articles/turning-sample-folders-into-mpc-kits-with-rust</guid>
            <pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Every producer I know has the same problem: gigabytes of drum samples and no kits. Building an MPC program by hand means assigning pads one sample at a time. It's the least musical part of making music.</p>
<p><strong>DorDSP Kit Pro</strong> fixes that. Point it at a folder of drums and it:</p>
<ol>
<li>Scans the library and classifies every hit by name: kick, snare, hats, percussion, and so on</li>
<li>Lays them out on a 16-pad bank</li>
<li>Writes a self-contained kit folder with the program and its samples, ready to copy onto the hardware</li>
</ol>
<p>It writes kits for DMC60 (<code>.dmcpgm</code>), any Akai MPC (<code>.xpm</code>), MPC 3 tracks (<code>.xtd</code>), Ableton Live Drum Racks (<code>.adg</code>), and Ableton Move and Note presets.</p>
<h2>One Rust core, two front ends</h2>
<p>The most important decision was keeping all the logic in Rust crates that know nothing about the UI:</p>
<pre class="language-text"><code class="language-text">crates/mpc-core/        scanning, classification, layouts, generation
crates/mpc-xpm/         .xpm parser + template-preserving writer
crates/mpc-xtd/         MPC 3 track writer with MPCe pad expression
crates/ableton-adg/     Ableton Drum Rack writer
crates/ableton-bundle/  Move/Note preset writer
crates/mpc-cli/         the dordsp-kit-pro binary
apps/desktop/           Tauri 2 + React
</code></pre>
<p>The desktop app is the main way in: drag a folder in, review the classification, preview the 4×4 pad grid, then generate. The CLI calls the exact same APIs, so it's great for batch jobs:</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># Twenty kits from one library, with minimal sample reuse across kits</span>
dordsp-kit-pro ./samples --kits <span class="token number">20</span> --output ./kits --name <span class="token string">"BoomBap"</span>

<span class="token comment"># One kit per subfolder, named after the folder</span>
dordsp-kit-pro ./Pack --per-folder --output ./kits

<span class="token comment"># Same variation every time, or a true shuffle</span>
dordsp-kit-pro ./samples --seed boomspot
dordsp-kit-pro ./samples --shuffle
</code></pre>
<p>Tauri gives me a small native app without shipping a whole browser, and Rust gives me fast, safe file and audio handling. None of the core crates depend on Tauri, so the engine stays testable on its own.</p>
<h2>MPC 3 and pad expression</h2>
<p>MPC 3 changed things: its factory kits are <em>tracks</em> (<code>.xtd</code>), not programs, and only tracks carry <strong>MPCe</strong> pad expression, meaning what a pad does depending on where you hit it. Kit Pro can write those with an expression preset applied to every pad:</p>
<table><thead><tr><th>Preset</th><th>What the pad does</th></tr></thead><tbody><tr><td><code>zones</code></td><td>Up to 4 samples, one per quadrant</td></tr><tr><td><code>blend</code></td><td>Two samples crossfaded bottom to top</td></tr><tr><td><code>spread</code></td><td>Up to 8 samples in strips from left to right</td></tr><tr><td><code>filter</code></td><td>X opens the low-pass filter, Y adds resonance</td></tr><tr><td><code>repeats</code></td><td>Outer zones trigger note-repeat articulations</td></tr></tbody></table>
<p>For <code>zones</code>, <code>blend</code>, and <code>spread</code>, the allocator stacks samples from the same category onto each pad: four kicks on pad 1, four hats on pad 3, and so on. The generated files follow the same structure as Akai's own factory tracks, but they still need testing on real MPC 3 hardware before I'd call them compatible.</p>
<h2>Cleaning up samples as they go in</h2>
<p>Two optional passes fix the most common problems with downloaded packs. Neither one touches your library, because processed samples are written as fresh WAV files inside the kit.</p>
<ul>
<li><strong>Trim leading silence</strong> (<code>--trim-start</code>) cuts everything before the first sample above -60 dBFS, keeping a 2 ms pre-roll so the transient never clicks. Pads trigger on the hit instead of a few milliseconds of air, which is what makes sloppy chops feel late or flam against each other.</li>
<li><strong>Normalize</strong> (<code>--normalize peak</code> or <code>--normalize loudness</code>). <code>peak</code> puts each sample's loudest point at -0.3 dBFS. <code>loudness</code> matches the RMS of the loudest 50 ms of each sample to -12 dBFS, so a kick and a snare come out at the same <em>perceived</em> level instead of the same peak.</li>
</ul>
<p>When you audition a pad in the app, you hear the processed audio, so what you hear is exactly what goes into the kit.</p>
<h2>Why this matters to me</h2>
<p>This tool came out of building <a href="/articles/building-dmc60-a-12-bit-drum-machine">DMC60</a>. I needed kits to test the drum machine, and making them by hand was killing my momentum. Now building a kit takes seconds, and I get back to the part I actually care about: making beats.</p>]]></content:encoded>
            <author>dorian.mcclintock@gmail.com (Dorian McClintock)</author>
        </item>
        <item>
            <title><![CDATA[Why I switched from React to Next.js for my projects]]></title>
            <link>https://dorianmcclintock.com/articles/why-i-switched-from-react-to-nextjs</link>
            <guid>https://dorianmcclintock.com/articles/why-i-switched-from-react-to-nextjs</guid>
            <pubDate>Fri, 22 Mar 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>For years, my go-to stack for any web project was Create React App with a separate API server. It worked fine, but I always felt like I was fighting against the tooling rather than working with it.</p>
<h2>The tipping point</h2>
<p>The final straw was building a website for a local vegan restaurant here in the Bay Area. They needed good SEO (obviously), fast load times on mobile, and the ability to update their menu themselves. My usual SPA approach wasn't cutting it.</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token comment">// The old way - client-side data fetching</span>
<span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/menu'</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span>res <span class="token arrow operator">=&gt;</span> res<span class="token punctuation">.</span><span class="token method function property-access">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token method function property-access">then</span><span class="token punctuation">(</span>setMenu<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

<span class="token comment">// The Next.js way - server components</span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function"><span class="token maybe-class-name">MenuPage</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> menu <span class="token operator">=</span> <span class="token keyword control-flow">await</span> <span class="token function">getMenu</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword control-flow">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">MenuList</span></span> <span class="token attr-name">items</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>menu<span class="token punctuation">}</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token punctuation">}</span>
</code></pre>
<p>That second approach is just... better. The data is already in the HTML when the page loads. No loading spinner, no content popping in after the fact, and search engines see the full menu on the first request.</p>
<h2>What I love about the App Router</h2>
<p>Next.js 14's App Router finally clicked for me after a few projects:</p>
<ol>
<li><strong>Server Components by default</strong> - Less JavaScript shipped to the client</li>
<li><strong>Nested layouts</strong> - No more prop drilling for navigation state</li>
<li><strong>Route Handlers</strong> - API endpoints live next to the pages, in one codebase</li>
<li><strong>Image optimization</strong> - The <code>next/image</code> component is magic</li>
</ol>
<h2>The learning curve is real</h2>
<p>I won't pretend the transition was seamless. Server vs. client components took some getting used to, and I definitely broke things by trying to use hooks in the wrong places.</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token comment">// ServerComponent.tsx - this will break, hooks don't work in server components</span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function"><span class="token maybe-class-name">ServerComponent</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment">// Error!</span>
  <span class="token keyword control-flow">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>count<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token punctuation">}</span>
</code></pre>
<p>The fix is to move interactive pieces into their own file and mark it as a client component. The <code>'use client'</code> directive has to be the very first line of that file:</p>
<pre class="language-tsx"><code class="language-tsx"><span class="token comment">// Counter.tsx</span>
<span class="token string">'use client'</span>

<span class="token keyword module">import</span> <span class="token imports"><span class="token punctuation">{</span> useState <span class="token punctuation">}</span></span> <span class="token keyword module">from</span> <span class="token string">'react'</span>

<span class="token keyword module">export</span> <span class="token keyword">function</span> <span class="token function"><span class="token maybe-class-name">Counter</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>
  <span class="token keyword control-flow">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">setCount</span><span class="token punctuation">(</span>c <span class="token arrow operator">=&gt;</span> c <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>count<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token punctuation">}</span>
</code></pre>
<h2>Worth the switch?</h2>
<p>For me, yes. My sites are faster, my code is cleaner, and I spend less time configuring build tools. For any project that needs good SEO or has dynamic content, Next.js is now my default choice.</p>
<p>The vegan restaurant site? Their search rankings improved noticeably within a month of launch. I can't credit that to server rendering alone, since the old site had other problems too, but serving real HTML on the first request certainly didn't hurt.</p>]]></content:encoded>
            <author>dorian.mcclintock@gmail.com (Dorian McClintock)</author>
        </item>
    </channel>
</rss>