<?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: AC0Hero</title>
    <description>The latest articles on DEV Community by AC0Hero (@ac0ai).</description>
    <link>https://dev.to/ac0ai</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%2F3836697%2Ff92e9b50-223f-4ed2-9d27-418b4076d772.jpg</url>
      <title>DEV Community: AC0Hero</title>
      <link>https://dev.to/ac0ai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ac0ai"/>
    <language>en</language>
    <item>
      <title>I Built an EU Law Tracker in 24 Languages. Then I Cut It Back to Four.</title>
      <dc:creator>AC0Hero</dc:creator>
      <pubDate>Thu, 13 Aug 2026 10:03:32 +0000</pubDate>
      <link>https://dev.to/ac0ai/i-built-an-eu-law-tracker-in-24-languages-then-i-cut-it-back-to-four-2bo2</link>
      <guid>https://dev.to/ac0ai/i-built-an-eu-law-tracker-in-24-languages-then-i-cut-it-back-to-four-2bo2</guid>
      <description>&lt;p&gt;The first version of &lt;a href="https://euinc.me/en" rel="noopener noreferrer"&gt;EU Inc Monitor&lt;/a&gt; had a simple internationalisation strategy: if a page existed in English, create it in every European language we supported.&lt;/p&gt;

&lt;p&gt;That gave us 24 language routes, a satisfyingly large sitemap, and a serious technical problem.&lt;/p&gt;

&lt;p&gt;Legal and regulatory content is not a normal localisation workload. A product landing page can survive a slightly stale translation. A page that explains a live legislative proposal cannot. The source document changes, committee positions diverge, dates move, and one misleading verb can turn a proposal into something that sounds like current law.&lt;/p&gt;

&lt;p&gt;The system now has four active locales, only two of which are indexed. That retreat made the site smaller, more accurate, and easier for search engines to understand.&lt;/p&gt;

&lt;p&gt;This is what changed in the Next.js architecture and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The original mistake: route count looked like coverage
&lt;/h2&gt;

&lt;p&gt;The application uses Next.js 16 and &lt;code&gt;next-intl&lt;/code&gt;. Every locale was a valid route prefix, so adding a language looked almost free:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineRouting&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeLocales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultLocale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;localePrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;always&lt;/span&gt;&lt;span class="dl"&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;But route availability is not content quality.&lt;/p&gt;

&lt;p&gt;With 24 locales, every new analysis created 24 public URLs. Search engines saw thousands of pages. Editors saw one English source and a translation backlog. When the European Commission published a correction or Parliament released a new amendment set, the English page could be updated immediately while the other 23 variants became progressively less reliable.&lt;/p&gt;

&lt;p&gt;The most dangerous failure mode was not a broken page. It was a polished, indexable page that still described yesterday's legal position.&lt;/p&gt;

&lt;p&gt;So we separated three concepts that had been incorrectly treated as one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A route can exist.&lt;/li&gt;
&lt;li&gt;A translation can be available to readers.&lt;/li&gt;
&lt;li&gt;A page can be approved for search indexing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those are different states.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smaller locale policy
&lt;/h2&gt;

&lt;p&gt;Today the policy is explicit in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indexedLocales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previewLocales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeLocales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;indexedLocales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;previewLocales&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;English and German are the search core. French and Spanish remain available as reader previews, but they are &lt;code&gt;noindex&lt;/code&gt; until they have passed native editorial review. The other 20 locales were retired instead of being left online as an unmaintained promise.&lt;/p&gt;

&lt;p&gt;This is a business rule expressed as a small data structure. It drives routing, robots directives, canonical tags, hreflang output, and sitemap generation.&lt;/p&gt;

&lt;p&gt;The key lesson was to avoid burying editorial policy inside page components. If five different files decide whether French should be indexed, they will eventually disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content availability is existence-driven
&lt;/h2&gt;

&lt;p&gt;Not every insight exists in every active locale. Older articles may have more translations, while a new analysis may initially have only English and German.&lt;/p&gt;

&lt;p&gt;The content loader therefore treats files on disk as the source of truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;getAvailableInsightLocales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;present&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;md$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&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="nx"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;present&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&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;That list is reused everywhere a language variant is exposed. A translation file is not merely copy. It is the capability that makes the corresponding route eligible to appear in navigation, metadata, and the insight sitemap.&lt;/p&gt;

&lt;p&gt;We still allow a reader-facing fallback to English in a few contexts. But a fallback URL must never be advertised to search engines as a real translated page. A URL that renders English under a Spanish path is useful as a convenience for a person and harmful as an SEO signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hreflang should describe reality, not ambition
&lt;/h2&gt;

&lt;p&gt;Our largest technical cleanup came from hreflang.&lt;/p&gt;

&lt;p&gt;The first implementation generated alternatives for every configured locale. Some of those URLs redirected. Others returned English fallback content. The framework also emitted an HTTP &lt;code&gt;Link&lt;/code&gt; header using unprefixed URLs, while our HTML metadata pointed to prefixed routes such as &lt;code&gt;/en/guide&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The result was two conflicting language clusters and hreflang entries that led to redirects.&lt;/p&gt;

&lt;p&gt;We disabled automatic alternate links and made Next.js metadata the single owner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineRouting&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeLocales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultLocale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;localePrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;always&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;alternateLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;Then we only emit alternatives that are both present and indexable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchLocales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;availableLocales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isIndexedLocale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;languages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;searchLocales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;getLocalizedPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&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="na"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getLocalizedPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentLocale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getLocalizedPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&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;The rule is strict: an hreflang target should return &lt;code&gt;200&lt;/code&gt;, be indexable, contain the matching content, and link back into the same cluster. If any of those conditions is false, the target does not belong in the cluster.&lt;/p&gt;

&lt;p&gt;This sounds obvious. It is also easy to violate when the list of languages comes from framework configuration rather than actual content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-canonicals, including on preview pages
&lt;/h2&gt;

&lt;p&gt;We considered pointing French and Spanish preview pages to the English canonical. That would have been wrong.&lt;/p&gt;

&lt;p&gt;A translated page is not a duplicate of the English page merely because both express the same facts. Cross-language canonicals can cause the translated URL to disappear from the signals search engines use to understand language variants.&lt;/p&gt;

&lt;p&gt;Our preview pages keep a self-canonical but use &lt;code&gt;noindex, follow&lt;/code&gt;. They do not emit an incomplete hreflang cluster. When a locale passes editorial review, indexing and hreflang can be enabled without changing the URL.&lt;/p&gt;

&lt;p&gt;That gives every page one clear state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;indexed and part of a reciprocal language cluster;&lt;/li&gt;
&lt;li&gt;available as a self-canonical preview with &lt;code&gt;noindex&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;or retired and redirected to the final English destination.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  We split the sitemap by intent
&lt;/h2&gt;

&lt;p&gt;The site has stable reference pages, live legislative trackers, and editorial analyses. They do not change at the same rate and should not be treated as one undifferentiated URL dump.&lt;/p&gt;

&lt;p&gt;We now generate a small core sitemap for hubs, methodology, sources, facts, and the live &lt;a href="https://euinc.me/en/timeline" rel="noopener noreferrer"&gt;legislative timeline&lt;/a&gt;. Insights live in a separate sitemap built from an allowlist of articles that have met sourcing and editorial requirements.&lt;/p&gt;

&lt;p&gt;The insight sitemap also reuses the locale policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;indexableInsightSlugs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;languages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;indexedLocales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nf"&gt;getLocalizedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`/insights/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;indexedLocales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getLocalizedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`/insights/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getLocalizedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`/insights/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&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;An article being published is no longer enough to put it in the search sitemap. Publication and index eligibility are separate decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The content model had to understand legal status
&lt;/h2&gt;

&lt;p&gt;Internationalisation was only half of the problem. A regulation tracker needs a shared status model so every page agrees on what has happened.&lt;/p&gt;

&lt;p&gt;We keep current status, recent developments, and upcoming legislative events in structured data. Pages read from that shared model instead of hand-writing the current position in every locale. Long-form analyses still live in Markdown, but volatile facts such as procedure stage and review date are centralised.&lt;/p&gt;

&lt;p&gt;That reduces the number of sentences an editor must hunt down after a new Council document appears.&lt;/p&gt;

&lt;p&gt;The public site also exposes its &lt;a href="https://euinc.me/en/methodology" rel="noopener noreferrer"&gt;methodology&lt;/a&gt;, &lt;a href="https://euinc.me/en/sources" rel="noopener noreferrer"&gt;primary-source register&lt;/a&gt;, and correction process. For legal information, those are product features, not footer decoration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do from day one
&lt;/h2&gt;

&lt;p&gt;If I were rebuilding the tracker, I would start with these constraints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Index one language first.&lt;/li&gt;
&lt;li&gt;Add a locale only when someone owns its ongoing editorial review.&lt;/li&gt;
&lt;li&gt;Represent route availability, translation availability, and index eligibility as separate states.&lt;/li&gt;
&lt;li&gt;Generate hreflang from real files and policy, never from the full framework locale list.&lt;/li&gt;
&lt;li&gt;Keep one metadata owner. Do not let middleware headers and page HTML describe different clusters.&lt;/li&gt;
&lt;li&gt;Test every hreflang target for &lt;code&gt;200&lt;/code&gt;, self-canonical, indexability, and reciprocity.&lt;/li&gt;
&lt;li&gt;Centralise volatile legal status instead of repeating it in prose.&lt;/li&gt;
&lt;li&gt;Retire weak locales cleanly rather than preserving them for vanity metrics.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The uncomfortable lesson is that international reach does not come from multiplying routes. It comes from maintaining trustworthy pages.&lt;/p&gt;

&lt;p&gt;The site became more useful when it shrank from 24 active languages to four, and more honest when only two of those were allowed into search. That is not a failure of internationalisation. It is internationalisation finally respecting the cost of the content it carries.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>i18n</category>
      <category>seo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Turned One Next.js Buying Guide Into 12 Sites. Here Is What Broke.</title>
      <dc:creator>AC0Hero</dc:creator>
      <pubDate>Wed, 12 Aug 2026 17:55:49 +0000</pubDate>
      <link>https://dev.to/ac0ai/i-turned-one-nextjs-buying-guide-into-12-sites-here-is-what-broke-7ni</link>
      <guid>https://dev.to/ac0ai/i-turned-one-nextjs-buying-guide-into-12-sites-here-is-what-broke-7ni</guid>
      <description>&lt;p&gt;A few months ago I wrote here about building a 209-page sauna site without knowing how to code.&lt;/p&gt;

&lt;p&gt;That experiment did not stay one site.&lt;/p&gt;

&lt;p&gt;It became 12 independent buying guides for saunas, cold plunges, hot tubs, heat pumps, home batteries, generators, EV charging, water filters, greenhouses, backyard offices, air conditioning, and longevity.&lt;/p&gt;

&lt;p&gt;You can see the network at &lt;a href="https://theguideguy.guide/" rel="noopener noreferrer"&gt;The Guide Guy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am still not a developer. I use AI tools to help me implement things. But I am responsible for the decisions, the operating model, and all the mistakes below.&lt;/p&gt;

&lt;p&gt;The surprising part was not generating another site. That became easy quickly.&lt;/p&gt;

&lt;p&gt;The hard part was keeping 12 small sites honest, deployable, measurable, and different enough to deserve their own existence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I did not make it one monorepo
&lt;/h2&gt;

&lt;p&gt;Each guide is its own repository and its own Vercel project.&lt;/p&gt;

&lt;p&gt;That was partly accidental. Sauna Guide came first, and the other sites were cloned from it. But the separation has been useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one broken build does not block every site&lt;/li&gt;
&lt;li&gt;each site can have its own brand, content rules, and deployment history&lt;/li&gt;
&lt;li&gt;a weak niche can be frozen without changing the stronger sites&lt;/li&gt;
&lt;li&gt;I can sell or hand off one property later without untangling a shared application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cost is drift.&lt;/p&gt;

&lt;p&gt;Every shared fix has to be applied deliberately. Environment variables diverge. One clone keeps an old CTA. Another has the wrong analytics key. A third looks healthy locally but deploys from the wrong commit.&lt;/p&gt;

&lt;p&gt;So I stopped treating the code template as the product.&lt;/p&gt;

&lt;p&gt;The operating system became the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: pnpm tried to be too helpful
&lt;/h2&gt;

&lt;p&gt;All projects live under one parent workspace on my machine. That made dependency installation look simple, until a site started resolving modules against the parent workspace instead of its own repository.&lt;/p&gt;

&lt;p&gt;The result was a build that worked in one context and failed in another.&lt;/p&gt;

&lt;p&gt;The boring fix was the right one: install inside each guide with &lt;code&gt;pnpm install --ignore-workspace&lt;/code&gt; when an install is actually needed.&lt;/p&gt;

&lt;p&gt;No abstraction. Just one explicit rule in the runbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: a successful deploy was not always the production deploy
&lt;/h2&gt;

&lt;p&gt;Vercel could report a deployment as Ready while the production domain still pointed at a different deployment.&lt;/p&gt;

&lt;p&gt;I also hit an author-identity problem. A commit could be technically valid but fail the production workflow because the Git author did not match the expected account.&lt;/p&gt;

&lt;p&gt;Now every deployment check asks two different questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Did a deployment finish successfully?&lt;/li&gt;
&lt;li&gt;Does the production target point to the exact commit I intended to ship?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those are not the same fact.&lt;/p&gt;

&lt;p&gt;This distinction sounds obvious after you learn it. It was not obvious while I was looking at a green status badge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: one invisible newline broke shared configuration
&lt;/h2&gt;

&lt;p&gt;Some environment values were added with a shell command that included a trailing newline.&lt;/p&gt;

&lt;p&gt;The value looked right in a dashboard. It was not right in the application.&lt;/p&gt;

&lt;p&gt;The network rule is now to pass exact values with &lt;code&gt;printf&lt;/code&gt;, not &lt;code&gt;echo&lt;/code&gt;, and verify what production actually uses before changing local files.&lt;/p&gt;

&lt;p&gt;This is a tiny lesson, but tiny lessons become expensive when copied across 12 sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 4: shared analytics created shared confusion
&lt;/h2&gt;

&lt;p&gt;The network uses a small number of analytics projects because the free plan has limits. Several domains therefore report into the same project.&lt;/p&gt;

&lt;p&gt;That only works if every report filters by hostname.&lt;/p&gt;

&lt;p&gt;At one point a local environment file pointed a site at the wrong analytics project while production was already correct. Editing production based only on the local file would have made the real system worse.&lt;/p&gt;

&lt;p&gt;The fix was procedural again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verify the production environment&lt;/li&gt;
&lt;li&gt;inspect real incoming events&lt;/li&gt;
&lt;li&gt;filter every report by domain&lt;/li&gt;
&lt;li&gt;treat local configuration and deployed configuration as separate evidence&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem 5: internal linking became a quality problem
&lt;/h2&gt;

&lt;p&gt;Twelve related sites make it tempting to build an all-to-all link mesh.&lt;/p&gt;

&lt;p&gt;That would be easy to automate and mostly useless to readers.&lt;/p&gt;

&lt;p&gt;Instead, links need to answer the next decision in the same project.&lt;/p&gt;

&lt;p&gt;A heat pump article may naturally link to a guide about home electrical capacity. A greenhouse article may link to water quality for a well. A random sauna article does not need links to eleven sister sites.&lt;/p&gt;

&lt;p&gt;I added an audit that checks whether approved links resolve, whether destinations repeat too often, and whether a site has become isolated. The audit can find structural gaps, but it cannot decide whether a link helps a human. That still needs judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The daily health report matters more than the template
&lt;/h2&gt;

&lt;p&gt;The most useful shared code is not a page component.&lt;/p&gt;

&lt;p&gt;It is a daily report that checks the boring things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;production domains&lt;/li&gt;
&lt;li&gt;search visibility&lt;/li&gt;
&lt;li&gt;analytics events&lt;/li&gt;
&lt;li&gt;AI referrals&lt;/li&gt;
&lt;li&gt;leads&lt;/li&gt;
&lt;li&gt;content-rule violations&lt;/li&gt;
&lt;li&gt;broken or suspicious cross-site links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used to think scaling meant making it faster to create another site.&lt;/p&gt;

&lt;p&gt;Now I think scaling means making it harder for an existing site to lie about being healthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;I would build the operating checks earlier and clone later.&lt;/p&gt;

&lt;p&gt;I would also spend less time thinking about the number of pages. Generic content is not a moat. The useful assets are current decisions, real quote data, honest verdicts, and relationships with people in each market.&lt;/p&gt;

&lt;p&gt;The technical system should make those things easier to maintain. It should not become an excuse to build more infrastructure.&lt;/p&gt;

&lt;p&gt;That is the biggest lesson from turning &lt;a href="https://sauna.guide/" rel="noopener noreferrer"&gt;Sauna Guide&lt;/a&gt; into a network that also includes projects like &lt;a href="https://heat-pumps.guide/" rel="noopener noreferrer"&gt;Heat Pump Guide&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;The clone script creates supply.&lt;/p&gt;

&lt;p&gt;Trust, distribution, and operational discipline create the business.&lt;/p&gt;

&lt;p&gt;I am still learning all three.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>seo</category>
      <category>devops</category>
    </item>
    <item>
      <title>EU Inc: What Developers and Founders Need to Know About Europe's New Company Form</title>
      <dc:creator>AC0Hero</dc:creator>
      <pubDate>Sat, 04 Apr 2026 10:41:14 +0000</pubDate>
      <link>https://dev.to/ac0ai/eu-inc-what-developers-and-founders-need-to-know-about-europes-new-company-form-3h37</link>
      <guid>https://dev.to/ac0ai/eu-inc-what-developers-and-founders-need-to-know-about-europes-new-company-form-3h37</guid>
      <description>&lt;p&gt;The European Commission dropped something big on March 18, 2026 - and many EU-developers missed it.&lt;/p&gt;

&lt;p&gt;They proposed &lt;strong&gt;EU Inc&lt;/strong&gt;: a single company form that lets you register once and operate across all 27 EU member states. No subsidiaries. No local notaries. No navigating 27 different legal systems.&lt;/p&gt;

&lt;p&gt;If you've ever tried to expand a business from one EU country to another, you know the pain. EU Inc is designed to kill that pain entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Registration time:&lt;/strong&gt; 48 hours, fully online&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; Under 100 EUR total&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimum capital:&lt;/strong&gt; 1 EUR (yes, one euro)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Countries covered:&lt;/strong&gt; All 27 EU member states&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notary required:&lt;/strong&gt; No&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physical presence required:&lt;/strong&gt; No&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare that to today: setting up a GmbH in Germany requires a notary, 25,000 EUR minimum capital, and weeks of paperwork. A Swedish AB needs 25,000 SEK. A French SAS needs complicated statutes drafted by a lawyer.&lt;/p&gt;

&lt;p&gt;EU Inc replaces all of that with one digital process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why developers should care
&lt;/h2&gt;

&lt;p&gt;If you're an indie developer selling SaaS across Europe, you currently face this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Today:&lt;/strong&gt; You register a company in your home country. You sell to customers in 15 EU countries. Tax compliance, VAT, and legal obligations vary per country. Want to hire someone in Germany? You probably need a local entity. Want to raise money from a French VC? They might want a local structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With EU Inc:&lt;/strong&gt; One company. One set of rules. You're already set up to operate everywhere.&lt;/p&gt;

&lt;p&gt;This matters especially for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Solo developers&lt;/strong&gt; selling digital products across the EU&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote-first startups&lt;/strong&gt; with team members in multiple countries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open source maintainers&lt;/strong&gt; looking to set up a commercial entity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indie hackers&lt;/strong&gt; who want to keep things simple while selling internationally&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What EU Inc actually is (technically)
&lt;/h2&gt;

&lt;p&gt;EU Inc is formally called the "28th regime" - it doesn't replace any existing national company forms. It adds a new one on top. The legal name is &lt;strong&gt;S.EU&lt;/strong&gt; (Societas Europaea Unius).&lt;/p&gt;

&lt;p&gt;Key governance features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Digital-first:&lt;/strong&gt; Registration, share transfers, and shareholder meetings can all happen online&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern cap table:&lt;/strong&gt; Built-in support for share classes, vesting, convertible instruments - things that currently require expensive legal workarounds in most EU countries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;English as standard:&lt;/strong&gt; Documentation can be in English regardless of which country you register in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single register:&lt;/strong&gt; One EU-wide company register connected via BRIS (Business Registers Interconnection System)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full proposal is &lt;a href="https://commission.europa.eu/topics/business-and-industry/doing-business-eu/company-law-and-corporate-governance/eu-inc-new-harmonised-corporate-legal-regime_en" rel="noopener noreferrer"&gt;COM(2026) 321&lt;/a&gt; if you want to read the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about taxes?
&lt;/h2&gt;

&lt;p&gt;This is the question everyone asks. The answer: &lt;strong&gt;EU Inc follows local tax rules&lt;/strong&gt; in the country where you register. Tax harmonization is not part of this proposal.&lt;/p&gt;

&lt;p&gt;My hope is that if you register your EU Inc in ex. Estonia (0% corporate tax on undistributed profits), you pay Estonian tax rules. Register in Ireland, you get Irish rates.&lt;/p&gt;

&lt;p&gt;This means jurisdiction shopping is still a factor - but at least you only need one entity instead of multiple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;January 2026:&lt;/strong&gt; European Parliament voted 492-144 in favor of EU Inc recommendations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;March 2026:&lt;/strong&gt; Commission published the formal proposal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End of 2026:&lt;/strong&gt; Target for Parliament and Council agreement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2027+:&lt;/strong&gt; First registrations expected (depends on adoption speed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's strong political will to push this through fast. The European Council explicitly called for it in their March 2026 conclusions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you wait for EU Inc?
&lt;/h2&gt;

&lt;p&gt;Probably not. If you need a company now, register one now. But you should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Track the timeline&lt;/strong&gt; - know when EU Inc becomes available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design for conversion&lt;/strong&gt; - EU Inc will likely allow conversion from existing national forms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand the trade-offs&lt;/strong&gt; - EU Inc is great for cross-border, but if you only operate in one country, your national form might still be simpler&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;I've been tracking this regulation closely and built a comprehensive resource site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://euinc.me/en/guide" rel="noopener noreferrer"&gt;Full EU Inc Guide&lt;/a&gt;&lt;/strong&gt; -- everything you need to know in one place&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://euinc.me/en/timeline" rel="noopener noreferrer"&gt;Interactive Timeline&lt;/a&gt;&lt;/strong&gt; -- tracks every legislative milestone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://euinc.me/en/compare" rel="noopener noreferrer"&gt;Company Form Comparison&lt;/a&gt;&lt;/strong&gt; -- EU Inc vs Delaware LLC, Swedish AB, German GmbH, Dutch BV side by side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://euinc.me/en/assessment" rel="noopener noreferrer"&gt;Readiness Assessment&lt;/a&gt;&lt;/strong&gt; -- 2-minute assessment to see if EU Inc fits your situation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://euinc.me/en/insights" rel="noopener noreferrer"&gt;Weekly Insights&lt;/a&gt;&lt;/strong&gt; -- analysis and legislation updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The site is available in 24 EU languages at &lt;a href="https://euinc.me" rel="noopener noreferrer"&gt;euinc.me&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What do you think -- would you use EU Inc for your next project? Drop a comment below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>eu</category>
      <category>business</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built a 209-Page Sauna Site Without Knowing How to Code</title>
      <dc:creator>AC0Hero</dc:creator>
      <pubDate>Thu, 02 Apr 2026 13:31:21 +0000</pubDate>
      <link>https://dev.to/ac0ai/i-built-a-209-page-sauna-site-without-knowing-how-to-codepublished-8c7</link>
      <guid>https://dev.to/ac0ai/i-built-a-209-page-sauna-site-without-knowing-how-to-codepublished-8c7</guid>
      <description>&lt;p&gt;I am not a developer. I want to say that upfront so you know what kind of post this is. What ive tried to do is figuring out distribution before figuring out code.&lt;/p&gt;

&lt;p&gt;The site is &lt;a href="https://sauna.guide" rel="noopener noreferrer"&gt;sauna.guide&lt;/a&gt;. It has 209 pages. Sauna listings, brand reviews, buying guides, gear recommendations. All of it built with Next.js, all of it static, all of it generated from JSON files and markdown.&lt;/p&gt;

&lt;p&gt;I did not write the code by hand. I used AI tools to help me build it. But the decisions, the outreach, the content strategy, the emails to manufacturers. That part is all me. I hope and believe that will make the difference.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Why saunas
&lt;/h2&gt;

&lt;p&gt;I love saunas. That is the whole origin story. No market research spreadsheet, no TAM analysis. I wanted to build something in a space I actually care about, and saunas felt right. There is a growing community, real buyer intent, and not a lot of good independent content online.&lt;/p&gt;

&lt;p&gt;So I started. 🧖&lt;/p&gt;

&lt;h2&gt;
  
  
  Distribution first, features second
&lt;/h2&gt;

&lt;p&gt;The biggest lesson so far: nobody cares about your site until you give them a reason to.&lt;/p&gt;

&lt;p&gt;I have spent more time emailing sauna manufacturers than I have spent on the site itself. Real emails, one by one. Introducing myself, explaining what Sauna Guide is, asking if they want their brand page reviewed and updated. Some reply. Some don't. All will ;) &lt;/p&gt;

&lt;p&gt;That is the whole distribution strategy right now. Talk to the people who make the things I write about. Build relationships. Get them to share their pages. &lt;/p&gt;

&lt;h2&gt;
  
  
  What 209 pages actually looks like
&lt;/h2&gt;

&lt;p&gt;Every page on the site comes from data files. Saunas are stored in a big JSON file with ratings, features, editorial notes, and quality scores. Guides are markdown files with a publish date in the header. Products have specs, prices, and Reddit sentiment.&lt;/p&gt;

&lt;p&gt;At build time, Next.js reads all these files and generates a static HTML page for each one. No database, no server, no CMS. Just files in a folder that become a website.&lt;/p&gt;

&lt;p&gt;I also built a simple scheduling system. Every guide has a date, and the site only shows guides where the date has passed. I have 14 guides queued through September. They go live on their own. All I need to do is redeploy every couple of weeks. 📅&lt;/p&gt;

&lt;h2&gt;
  
  
  Early signs
&lt;/h2&gt;

&lt;p&gt;I want to be honest here. I do not know if this will work long term. The site has been live for a few months and I am still figuring things out.&lt;/p&gt;

&lt;p&gt;But some things are starting to move:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google is indexing pages and some are ranking on page one for specific queries&lt;/li&gt;
&lt;li&gt;A few brand pages get steady organic traffic&lt;/li&gt;
&lt;li&gt;Manufacturers who engaged with outreach are sharing their pages&lt;/li&gt;
&lt;li&gt;People who find the site tend to stay and read multiple pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is explosive growth. It is more like seeds starting to show above the soil. Enough to keep going. Enough to believe there is something here.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I have learned so far
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Content quality beats content quantity.&lt;/strong&gt; I started by trying to publish as much as possible. That was a mistake. The pages that rank are the ones I spent real time on. The ones with editorial opinions, real tips, honest drawbacks. The thin pages just sit there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outreach is the unlock.&lt;/strong&gt; SEO takes months. Social media is noisy. But a personal email to a manufacturer who then shares your review of their product? That is immediate, targeted traffic from exactly the right audience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You do not need to code to build.&lt;/strong&gt; I am proof of that. AI tools can handle the implementation if you know what you want to build and why. The hard part is not the code. The hard part is knowing what to say, who to talk to, and having the patience to wait for results.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;More outreach. More guides. More relationships with people in the sauna world. I am also working on a quiz that helps people find the right home sauna, which feeds into an email sequence. &lt;/p&gt;

&lt;p&gt;I do not have a grand vision for where this ends up. Maybe it becomes a real business. Maybe it stays a passion project that makes a little money. Either way, I am grateful to God for the ability to build something from nothing and learn along the way. That alone has been worth it. 🙏&lt;/p&gt;

&lt;p&gt;If you are thinking about building a content site, my advice is simple: pick something you love, build the pages, and then spend most of your time talking to people instead of tweaking your code.&lt;/p&gt;

&lt;p&gt;The site is at &lt;a href="https://sauna.guide" rel="noopener noreferrer"&gt;sauna.guide&lt;/a&gt; if you want to see it.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>seo</category>
      <category>startup</category>
    </item>
    <item>
      <title>I built a church discovery site with 2,200+ churches in 50 countries</title>
      <dc:creator>AC0Hero</dc:creator>
      <pubDate>Sat, 21 Mar 2026 11:52:10 +0000</pubDate>
      <link>https://dev.to/ac0ai/i-built-a-church-discovery-site-with-2200-churches-in-50-countries-4bhk</link>
      <guid>https://dev.to/ac0ai/i-built-a-church-discovery-site-with-2200-churches-in-50-countries-4bhk</guid>
      <description>&lt;p&gt;&lt;strong&gt;GospelChannel is like "Yelp for churches" but with worship playlists, YouTube links, and a prayer wall.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm building this
&lt;/h2&gt;

&lt;p&gt;Finding a church shouldn't be hard. But it is. You google "churches near me" and get a mix of outdated directories, broken websites, and listings that tell you almost nothing useful.&lt;/p&gt;

&lt;p&gt;I moved cities a few years ago and spent weeks trying to find a church that felt right. Service times were wrong. Websites were down. There was no easy way to get a feel for a place before showing up.&lt;/p&gt;

&lt;p&gt;So I started building GospelChannel. Not as a business plan or a startup pitch. Just as something I wished existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the site actually does
&lt;/h2&gt;

&lt;p&gt;Every church on GospelChannel gets a profile page with the stuff that actually matters when you're looking for a place to attend:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worship playlists from Spotify&lt;/strong&gt; so you can hear what the music sounds like before you visit. This is a bigger deal than people think. Music sets the tone for an entire service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;YouTube links&lt;/strong&gt; for each church, so you can watch a service from your couch before deciding to visit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service times&lt;/strong&gt; that are actually current (more on how I keep these updated below).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A prayer wall&lt;/strong&gt; where people can share and support each other. In all honesty, the prayers on there right now are seeded since the site doesn't have real traffic yet. But the feature is built and ready for when people start showing up.&lt;/p&gt;

&lt;p&gt;You can browse by country, city, or denomination. The goal is: land on the site, find a church, and know what to expect before you walk through the door.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tech behind it
&lt;/h2&gt;

&lt;p&gt;The site runs on &lt;strong&gt;Next.js 16&lt;/strong&gt; with &lt;strong&gt;Tailwind&lt;/strong&gt; for styling, &lt;strong&gt;Supabase&lt;/strong&gt; as the database and auth layer, and &lt;strong&gt;Vercel&lt;/strong&gt; for hosting. Pretty standard modern stack, nothing fancy.&lt;/p&gt;

&lt;p&gt;The interesting part is the data pipeline. Manually researching 2,200+ churches would take forever, so I built an AI pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Apify&lt;/strong&gt; crawls church websites&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firecrawl&lt;/strong&gt; extracts structured data from those pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; enriches and normalizes everything: service times, denominations, locations, descriptions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pipeline lets me add churches in bulk and keep the data fresh without doing everything by hand. It's not perfect, but it gets me 80% of the way there, and I can review and correct the rest.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Spotify API&lt;/strong&gt; handles the playlist integration. Each church gets matched with their official Spotify profile when one exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers so far
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2,200+&lt;/strong&gt; churches listed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;50&lt;/strong&gt; countries covered&lt;/li&gt;
&lt;li&gt;Growing steadily through the AI pipeline and community submissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's still early and traffic is basically zero. But the data is there, the features work, and now it's time to get it in front of people. That's partly why I'm writing this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;A few things on my list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Better search and filtering.&lt;/strong&gt; Right now it's functional but basic. I want filters for language, accessibility, denomination, and service style.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Church claiming.&lt;/strong&gt; Let pastors and admins claim their church profile and keep it updated themselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More countries and languages.&lt;/strong&gt; 50 countries is a start, but there are churches everywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Check it out
&lt;/h2&gt;

&lt;p&gt;If you're curious, the site is live at &lt;a href="https://gospelchannel.com" rel="noopener noreferrer"&gt;gospelchannel.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'd love your feedback. Whether it's about the tech, the UX, or churches you think should be listed. Drop a comment here or reach out through the site.&lt;/p&gt;

&lt;p&gt;And if you know someone who's looking for a church, maybe send them the link. That's the whole point of this thing.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>sideprojects</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
