<?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: zhaoq</title>
    <description>The latest articles on DEV Community by zhaoq (@zhaoq0103).</description>
    <link>https://dev.to/zhaoq0103</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4210959%2F9f23e0b1-07f3-48a3-a3e9-f0fed551e1bc.jpg</url>
      <title>DEV Community: zhaoq</title>
      <link>https://dev.to/zhaoq0103</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zhaoq0103"/>
    <language>en</language>
    <item>
      <title>Running 48 Next.js sites from one laptop: what the tooling actually costs</title>
      <dc:creator>zhaoq</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:37:22 +0000</pubDate>
      <link>https://dev.to/zhaoq0103/running-48-nextjs-sites-from-one-laptop-what-the-tooling-actually-costs-3mkf</link>
      <guid>https://dev.to/zhaoq0103/running-48-nextjs-sites-from-one-laptop-what-the-tooling-actually-costs-3mkf</guid>
      <description>&lt;p&gt;I did not set out to run 48 websites. It was supposed to be one.&lt;/p&gt;

&lt;p&gt;A year ago I started a small wiki for an indie game — game guide pages, the kind of thing where "how do I beat this boss" gets 200 searches a month and nobody has written it down. That one site worked well enough that the obvious next move was a second, then a fifth, then a template and a script.&lt;/p&gt;

&lt;p&gt;At 48 sites the problems stopped being about writing pages and started being about tooling. Here is what it actually costs, in case you are tempted to do the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack is boring, on purpose
&lt;/h2&gt;

&lt;p&gt;Every site is the same Next.js app: static export, a &lt;code&gt;content/guides/*.md&lt;/code&gt; folder, and three config files (&lt;code&gt;lib/seo-config.ts&lt;/code&gt;, &lt;code&gt;lib/adsterra-zones.ts&lt;/code&gt;, &lt;code&gt;lib/site.ts&lt;/code&gt;). No database, no API routes, no server state.&lt;/p&gt;

&lt;p&gt;That is the whole point. When 48 sites share one codebase shape, a fix is written once and applied 48 times. When they diverge, maintenance becomes a full-time job. The number of times I have been glad a site had a custom layout is zero; the number of times a shared &lt;code&gt;components/&lt;/code&gt; folder saved me is every week.&lt;/p&gt;

&lt;h2&gt;
  
  
  pnpm is not a preference, it is disk space
&lt;/h2&gt;

&lt;p&gt;With npm, 48 sites with a shared dependency set came to &lt;strong&gt;18.7 GB&lt;/strong&gt; of &lt;code&gt;node_modules&lt;/code&gt;. Same versions, copied 48 times.&lt;/p&gt;

&lt;p&gt;Switching to pnpm's content-addressable store brought that to &lt;strong&gt;479 MB&lt;/strong&gt; — one real copy, hard-linked into every project. Same &lt;code&gt;node_modules&lt;/code&gt; layout from the app's point of view, 39× less disk.&lt;/p&gt;

&lt;p&gt;If you run more than a handful of similar projects on one machine, this alone is worth the migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deploy quota is the real bottleneck
&lt;/h2&gt;

&lt;p&gt;Vercel's free tier allows 100 deployments per day. Sounds like plenty until a bulk change touches every site — renaming one metadata field across 48 projects is 48 deployments, and they do not roll up.&lt;/p&gt;

&lt;p&gt;So deployments have to be queued. Mine live in a plain text file, one site per line, and a script drains it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# drain the queue, respecting today's remaining quota&lt;/span&gt;
scripts/deploy-pending.sh &lt;span class="nt"&gt;--limit&lt;/span&gt; 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two rules came out of getting this wrong:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The local deploy log is not the quota.&lt;/strong&gt; Dashboard deploys, git-integration deploys and anyone clicking "Redeploy" all count. My log said 65 used when the API said the limit was gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A quota error is not a bug.&lt;/strong&gt; It exits zero, leaves the queue untouched, and the next cron run picks up where it stopped. Treating it as a failure just produces noise every day.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Static sites fail in DNS, not in code
&lt;/h2&gt;

&lt;p&gt;Both sites and the tooling that reads them run behind hostile DNS. &lt;code&gt;suggestqueries.google.com&lt;/code&gt; resolves to a corporate IP; &lt;code&gt;*.vercel.app&lt;/code&gt; resolves to different addresses on different resolvers.&lt;/p&gt;

&lt;p&gt;Every verification script therefore does the same two steps — resolve through a public resolver, then connect to that IP explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dig @1.1.1.1 +short &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; A | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--resolve&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;:443:&lt;/span&gt;&lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;/page"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;--resolve&lt;/code&gt;, a poisoned answer turns into "the site is down" reports that are not true. With it, verification is boring and trustworthy. If you automate any kind of site checking, build this in from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gates, not vibes
&lt;/h2&gt;

&lt;p&gt;Every content change goes through three checks before it deploys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata length&lt;/strong&gt; — titles are validated to 40–60 characters, descriptions to 140–160. Sounds pedantic until you measure a batch and find a third of them truncated in search results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template residue&lt;/strong&gt; — a grep for placeholder text, TODO markers and copy-paste from the base template.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live re-verification&lt;/strong&gt; — after deploy, the script fetches the page and checks the &lt;em&gt;rendered&lt;/em&gt; title, not the source file. Source correct and live correct are two different claims, and I have shipped the first without the second more than once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The third one is the one people skip. The last batch I ran had 21 sites pass in source and one fail live for a reason that had nothing to do with the change — a transient fetch timeout that would have looked like a real failure if the script had not been re-run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell someone starting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Make the first site genuinely good. The template is worthless if the content underneath is not worth reading.&lt;/li&gt;
&lt;li&gt;Automate the checks before you automate the content.&lt;/li&gt;
&lt;li&gt;Keep the deploy queue in a file, not in your head.&lt;/li&gt;
&lt;li&gt;Budget for the boring parts: DNS, quotas, metadata length, dead links.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the sites, &lt;a href="https://nivalisnightswiki.vercel.app" rel="noopener noreferrer"&gt;Nivalis Nights Wiki&lt;/a&gt;, now gets a few thousand pageviews a month, and its &lt;a href="https://nivalisnightswiki.vercel.app/guides/beginner-guide" rel="noopener noreferrer"&gt;beginner guide&lt;/a&gt; is the single most-read page on it. Not a business yet, but the loop works: find a question nobody answered, answer it properly, keep the site from rotting.&lt;/p&gt;

&lt;p&gt;The tooling is the easy part once you accept that the interesting problems are all in the seams — between the build and the deploy, between the source file and the live page, between what the log says and what the API counts.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>devops</category>
      <category>seo</category>
    </item>
  </channel>
</rss>
