Our First npm Package: Why We Shared astro-split-domains

6 min read Auf Deutsch lesen

In short

We published our build script as an open-source package because the gap is not ours alone: Astro has no built-in way to serve a static website from several domains. Since 11 October 2026, @codeaeffchen/astro-split-domains 0.1.0 has been on npm, and our own website installs it from there. First result: 657 of 667 files stayed byte-identical, and the other 10 had a bug we only found while extracting the code. Whether anyone else uses the package cannot be said after four days.

Last weekend we published our first npm package: @codeaeffchen/astro-split-domains. It serves a static Astro website from one domain per language. For us, that means German on codeaeffchen.de and English on codeaeffchen.com, both from one build.

How this works technically and what to watch with two domains is covered in Multilingual Astro: Two Domains, Two Languages, One Build. This article is about the other side: why we published an internal script at all, what took the work, and what has come of it in the first few days.

Where we started: a script running since April

Since April 2026, our repository had a file called build-split.mjs. It built the website and then split the output into two folders, one per domain. By the end it was 225 lines long and ran on every deploy.

The script was made for exactly one website. The domains were hard-coded, and so was the sitemap name. There was a special rule for llms.txt, because each language has its own version. It worked, but only for us, and only as long as nobody asked why line 81 looked the way it did.

Why publish instead of leaving it there

The gap is not ours alone. Astro can put languages on their own domains, but according to the Astro documentation the i18n.domains option requires server output. Anyone with a fully static website runs into the same problem we did. Our solution was already running, so we are making it available.

Public code has to explain itself. A script in your own repository can rely on assumptions that are written down nowhere. A package other people install cannot. We wanted that scrutiny for a part of our build that touches more than 400 pages on every deploy.

We use it like everyone else. Our website installs the package from npm, not from a folder next door. If an update breaks something, we notice it on our own deploy before anyone else has to open an issue.

It shows how we work. Anyone thinking about giving us an Astro project can look at the code, the tests and the documentation instead of relying on a list of references.

What extracting it actually involved

Copying the file and uploading it would not have been enough. The real work:

  • Options instead of hard-coded values. Domains, locales, the sitemap name and the markers in .htaccess are now settings. Our special rule for llms.txt became a general option for files each language generates itself.
  • An Astro integration instead of a script. The old script called astro build itself. The integration hooks into the build and runs afterwards. It is added to the Astro config like any other integration.
  • Tests. 13 tests cover links, absolute URLs, sitemaps, robots.txt, the error page and .htaccess. They run on GitHub on every change, on Node 20 and Node 22.
  • No dependencies of its own. Apart from Astro itself, the package needs nothing. That is why our dependency security check found nothing new after the switch.
  • Limits in the documentation. The README also says what the integration does not do: no server-rendered pages, no hreflang tags of its own, no URLs that only come into being in the browser through JavaScript.
  • MIT licence. Anyone may use the package, including in client projects, without asking.

On 11 October, version 0.1.0 went to npm, and our website switched over the same morning. In the website’s repository, 232 lines were removed and 45 added.

The cross-check: 667 files

Before the website switched to the package, we ran both against each other: from the same state, once with the old script and once with the package. Both produced 667 files. 657 of them were byte-identical.

The other 10 were not new bugs but old ones. They were small redirect pages that Astro generates for moved URLs. Their canonical had been pointing to addresses like https://codeaeffchen.com/…, which no longer exist after the split. The old script had missed this spelling; the package rewrites it to the English domain.

If we had not prepared the script for other people, we would probably never have noticed. The pages are set to noindex and redirect immediately. Nobody saw the bug, but it was in every build.

First results after four days

As of 15 October 2026, broken down honestly:

  • npm: 465 downloads, 398 on 11 October and 67 on 12 October. The number says little. On 11 October our own pipelines ran around 40 times because we changed a lot that day, and npm also counts automated mirrors and scanners. Since our pipelines presumably fetch the package from their cache, npm has counted hardly any downloads.
  • GitHub: no stars, no issues, no forks. GitHub reports 63 clones from 34 different sources between 10 and 12 October. GitHub does not show who they are. Some of them are the package’s own test runs.
  • Astro catalogue: Thanks to the keywords in package.json, the package appeared automatically in the integrations directory on astro.build, still labelled “new”.
  • Our website: Every deploy since 11 October has run through the package, with no issues.

So far there is no external evidence that anyone else is using the integration. We expected that: the package solves a niche problem, and people who need it usually only start looking once a second domain is on the table. The tangible benefit so far is ours: less code in our own repository, tests for a critical build step and one fixed bug.

When this is worth it for a small company

We would do it again, but not with every piece of code. Our criteria:

  1. The problem is general, not tied to one client or project.
  2. The code already runs in production, for ourselves. We do not publish anything we do not use every day.
  3. It is small enough to maintain. The package has 314 lines of code. We can handle that alongside client work.
  4. Client code stays client code. What we build for clients belongs to them. We only publish what we built for ourselves.

Publishing code comes with a small responsibility: answering questions, keeping up with new Astro versions, fixing bugs. We read issues, but we do not promise a fixed response time.

What happens next

Version 0.1.0 means the integration runs reliably for us, but the options may still change once other websites bring other requirements. We check it with every major Astro update, because our website is the first to go through it.

Source code, documentation and issues are on GitHub. How the website around it is built is described in the codeaeffchen.de case study.


Want to serve a static Astro website from several domains, or have a question about the integration? Open an issue on GitHub, or get in touch directly if it is about your project.

Frequently Asked Questions

What does @codeaeffchen/astro-split-domains do?

The integration splits a static Astro build into one folder per domain after the build, for example German for example.de and English for example.com. It adapts links, canonicals, hreflang URLs, sitemaps, robots.txt, the error page and, optionally, the .htaccess to each domain.

Can I use the package commercially?

Yes. It is MIT-licensed and may be used, modified and redistributed in your own and in client projects. The licence notice has to be kept.

What does the integration require?

Astro 5 or later and Node.js 20 or later, plus static output. Pages rendered on the server for each request are not touched. The package has no other dependencies.

What do the download numbers on npm tell you?

Little about real users. npm counts every fetch from the registry, including the publisher's own build pipelines and automated mirrors. For a new package, the first numbers therefore often come largely from the publisher. Issues, pull requests and dependent packages say more.

Related Projects

Daniel Nilges
Daniel Nilges

Founder & Full-Stack Developer

20+ years of web development experience. Specialised in Laravel, WordPress and custom software for mid-sized businesses.