npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bablic-io/sdk-astro

v1.2.0

Published

Translate your Astro site with Bablic: server-rendered, SEO-indexable pages in every language.

Readme

@bablic-io/sdk-astro

Server-side rendering for Bablic on Astro. Your translated pages are rendered on the server, so visitors and search engines receive finished, indexable HTML in their language. In the browser, Bablic keeps translating content that appears later. Built for Astro 6 on @astrojs/vercel; works with any Node adapter.

Next.js site? Use @bablic-io/sdk-vercel instead. This package is for Astro only, on Vercel or any Node host.

You need a Bablic account and project. New to Bablic? Sign up.

Requirements

  • A Bablic project with the Bablic script tag on every page, in the HTML the server sends.
  • A Bablic Pro plan or a free trial. On other plans the SDK serves the original page unchanged (x-moxie: skipped, x-moxie-reason: please subscribe to pro tier) and Bablic keeps translating in the browser. After a plan change, allow about 15 minutes. Plans: Bablic dashboard.
  • Translated URLs set in your Bablic dashboard as language folders (for example example.com/fr-fr/…), or language subdomains on sites rendered on demand. Cookie and query-parameter setups have no separate URL per language, so they keep translating in the browser only: the SDK serves their pages unchanged (x-moxie: skipped), even with a custom resolve.
  • The dashboard folders must be exactly the paths you want: a folder /fr-fr/ serves /fr-fr/…, never /fr/….
  • Astro 6 with a Node adapter (@astrojs/vercel 10 or later on Vercel).
  • Node.js 22.

Install

npm install @bablic-io/sdk-astro

Set up

  1. Keep the Bablic script tag in your base layout. is:inline keeps Astro from bundling it, so it reaches the server HTML.

    <script is:inline src="https://edge.bablic.com/moxie.min.js" data-oljs="P0000"></script>

    Copy the exact tag from your Bablic dashboard. P0000 stands for your project key. Don't add the tag from client-side code or a tag manager.

    Add data-glweb-opacity="1" to the tag. Without it, the Bablic script hides each original-language page (near-zero opacity) until its settings arrive, which costs first paint and Lighthouse score. Translated pages served by the SDK never hide; the SDK sets this attribute on them. The tag can also load with defer. Keep the P at the start of data-oljs.

  2. Add a route or middleware with your project key, depending on how your pages render (below). The SDK reads your translated URLs from your Bablic dashboard settings, so you don't list languages or URLs in code.

  3. Vercel Preview deployments with Deployment Protection: in your Vercel project, open Settings → Deployment Protection, turn on Protection Bypass for Automation, then redeploy. Without it, the SDK's fetch of each original page is sent to the Vercel login and no page is translated. The SDK sends the secret Vercel provides (VERCEL_AUTOMATION_BYPASS_SECRET) on its own.

  4. Preview domains: Bablic only answers for domains your project allows, and link rules are set per domain. Add the preview's domain as your project's staging domain in the dashboard. Use the branch's stable domain (<project>-git-<branch>-<team>.vercel.app) or a custom preview domain, since each deployment's own URL changes.

Prerendered pages: moxieRoute

@astrojs/vercel runs middleware for prerendered pages at build time only, so add an on-demand catch-all route. Your prerendered pages take precedence; only URLs without a page reach it.

// src/pages/[...path].ts
import { moxieRoute } from '@bablic-io/sdk-astro';

export const prerender = false;
export const GET = moxieRoute({ project: 'P0000' });
export const HEAD = GET;

Translated URLs are served translated. Any other URL that reaches the route gets 404, as it would without it, with x-moxie: none and an x-moxie-reason saying why.

  • Remove prerendered pages under your language folders. A page such as src/pages/fr/index.astro is served instead of the translation, so the SDK never sees /fr/. Keep one copy of each page, in your original language.

  • Use language folders with this setup. A language subdomain shares its paths with your prerendered pages, so the route never sees those requests.

  • Already have a catch-all route at the root? Call moxieRoute from it first and fall back to your own handling when it returns 404:

    // src/pages/[...path].ts
    import type { APIRoute } from 'astro';
    import { moxieRoute } from '@bablic-io/sdk-astro';
    
    export const prerender = false;
    const bablic = moxieRoute({ project: 'P0000' });
    
    export const GET: APIRoute = async (context) => {
      const translated = await bablic(context);
      if (translated.status !== 404 || translated.headers.get('x-moxie') !== 'none')
        return translated;
      return yourHandler(context); // your existing catch-all logic
    };
    export const HEAD = GET;
  • Edge middleware keeps working. With middlewareMode: 'edge', your middleware runs before the route for translated URLs, and again on the SDK's fetch of the original page, so your redirects and status codes (410, 404) apply to translated URLs too. Only moxieMiddleware needs the Node.js runtime.

On-demand pages: moxieMiddleware

// src/middleware.ts
import { sequence } from 'astro:middleware';
import { moxieMiddleware } from '@bablic-io/sdk-astro';

export const onRequest = sequence(moxieMiddleware({ project: 'P0000' }) /*, your middleware */);

It skips prerendered pages and supports language folders and subdomains. It runs Bablic in your middleware, so it needs the Node.js runtime: don't combine it with middlewareMode: 'edge'. For edge middleware, use moxieRoute.

Check it works

curl -sI https://<your-translated-url>        # x-moxie: MISS, then HIT on a second request

On a protected Vercel Preview, add the bypass secret: curl -sI -H "x-vercel-protection-bypass: <secret>" https://<your-translated-url>.

View the page source: <html … data-moxie-mode="ssr"> and one <script type="application/json" id="moxie-manifest">.

While your project is in Staging, add ?bablic_preview=1 to the URL. Staging responses are never cached.

Every response the SDK handles carries x-moxie; all but HIT, STALE, MISS and bypass also carry x-moxie-reason:

| x-moxie | Meaning | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | HIT | Served from the cache. | | STALE | Served from the cache while a newer translation is prepared in the background. | | MISS | Translated for this request, then cached. | | none | Not a translated URL (404). The reason lists the folders the SDK sees and how old its config is, or why the config couldn't be read. | | redirect | The original page redirects; Location follows your link rules. The reason shows both URLs. | | pass | The original page isn't a 200 HTML page (404, 410, JSON …); served as is. | | skipped | Not translated on the server: your plan doesn't include it, or the project doesn't translate this page (not live, no script tag, or translation off). | | failed | Translation failed; the original page was served under the failure rule. | | bypass | Request from the Bablic editor; original page, uncached. |

When you change your languages or URLs

The SDK reads your project settings from Bablic and caches them for 5 minutes; Bablic's own cache adds up to 10. A change in the dashboard reaches the SDK within about 15 minutes, with no code change or redeploy. x-moxie-reason on a 404 shows the folders the SDK currently sees. To drop the SDK's copy sooner, POST {"config": true} to the revalidate route (Clearing the cache).

How it works

  1. A request for a translated URL reaches the route or middleware. A cached translation is served at once (x-moxie: HIT).
  2. On a miss, the SDK fetches the original page from the same deployment (for a language folder, the same path without the folder; the CDN serves prerendered pages) and translates it on the server with your project's translations.
  3. The translated page is cached and served (x-moxie: MISS).
  4. In the browser, the Bablic script translates content added later, including text that islands (client:*) render, without redoing the server's work.

Links and redirects follow your project's link rules in Bablic, for the domain the request is on. The SDK never invents a URL: with no rules, links stay as they are. The translated page's <html lang> is the language's code from your dashboard (unless your project sets its own HTML language), so browsers don't offer to translate it again. Translated URLs keep the original path under the language folder (/fr-fr/pricing); translated slugs aren't supported yet.

If translation fails or times out, nothing partial is served or cached: search engines get 503, visitors get the original page with noindex.

Clearing the cache

Pages refresh on their own after pageTtl (1 hour). To clear them sooner, add a signed purge route:

// src/pages/api/bablic/revalidate.ts
import type { APIRoute } from 'astro';
import { createRevalidateHandler, runtimeCache } from '@bablic-io/sdk-astro';

export const prerender = false;
const revalidate = createRevalidateHandler({
  secret: import.meta.env.MOXIE_REVALIDATE_SECRET,
  cache: runtimeCache(),
});
export const POST: APIRoute = ({ request }) => revalidate(request);

POST {"tkey":"<translation key>"} (add "url_hashes": [...] for single pages), or {"config": true} to re-read your project settings, with the header x-moxie-signature: sha256=<hex HMAC-SHA256 of the body, keyed with the secret>.

Options

project, pageTtl, staleTtl, timeoutMs, backgroundTimeoutMs, cache, editorCookie, onResult, onUnsupported, resolve, moxie, host, fetch, plus buildId and sourceHeaders. Defaults are described in the @bablic-io/sdk-vercel README.

SEO

What the SDK does on every translated page:

  • <html lang> and the content-language header carry the language's code from your dashboard, as a BCP-47 tag (fr-FR).
  • A canonical, og:url or twitter:url that names the original page is pointed at the translated URL, so each language is its own page, not a duplicate. A canonical that names a different page is left alone.
  • Titles, descriptions, Open Graph and Twitter tags, JSON-LD and image alt text are translated with the rest of the page.
  • JSON-LD (structured data) points at the translated page: url, item, mainEntityOfPage and @id values that name a page of your site get the translated URL, and inLanguage the page's language. Site-wide nodes, whose @id is your home page plus a # name (#organization, #website), stay the same on every page. Files and other sites' URLs are left alone.
  • hreflang values are valid BCP-47 tags. A language code with a private-use region, such as Spanish (Universal) ES-XM, is written es, in hreflang and in og:locale, since search engines ignore a region that doesn't exist.

What your project and site decide:

  • hreflang. Bablic adds alternates for every language plus x-default to translated pages when hreflang is enabled for your project (ask Bablic support), and only on pages with no hreflang tags of their own. Your original-language pages need the same set, and prerendered pages never pass through the SDK, so add the build step below. Don't print hreflang tags yourself: a partial set (only en and x-default) stops Bablic from adding the rest.

hreflang on your original pages

bablicHreflang writes the hreflang set into each prerendered page when the site builds, from your project's languages in the Bablic dashboard. Translated pages still get theirs from Bablic, with the same links.

// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel';
import { bablicHreflang } from '@bablic-io/sdk-astro';

export default defineConfig({
  site: 'https://<your-domain>', // the links' origin
  adapter: vercel(),
  integrations: [bablicHreflang({ project: 'P0000' })],
});
  • Rebuild after adding or removing a language in the dashboard. The build log says how many pages got links.
  • It follows your trailingSlash setting (/fr or /fr/ for a language home page).
  • Pages are left alone when they have no Bablic script tag, have their own hreflang tags, are noindex, have a canonical naming another page, or sit under a language folder. 404 and 500 pages too.
  • If the project config can't be read, the build goes on without links and logs a warning.
  • Sitemap. The SDK doesn't change your sitemap. Add each language's URLs (your original paths under each language folder) with your sitemap tool.
  • Images. og:image and other images stay as your site sets them, unless your project translates images.
  • Links. Links follow your project's link rules for the domain the page is served on. A domain with no rules keeps the original links, so set rules for your production domain and for any staging or preview domain.

Caching and speed

  • Fresh for pageTtl (1 hour): served from the cache (x-moxie: HIT).
  • Stale after that, or after a new deploy: still served at once (x-moxie: STALE), and refreshed in the background. After a deploy, a page whose original HTML is unchanged keeps its translation (one fetch of the original, no new translation). An expired page is always translated again, so edits in Bablic show up within pageTtl.
  • Kept for staleTtl (7 days) after it expires; only a page nobody visits that long is translated while a visitor waits.
  • Slow first translation: if a page's first translation runs past timeoutMs, the visitor gets the failure rule and the SDK finishes it in the background (backgroundTimeoutMs, 15 s), so the next visitor gets a HIT.
  • Warming: request each translated URL once after a deploy (without ?bablic_preview=1, which is never cached) to fill the cache before visitors arrive.
  • Server-Timing on every response shows where the time went, for example mx;desc="MISS", cache;dur=3, source;dur=42, translate;dur=870, total;dur=918. Browser dev tools and Lighthouse show it.

Hosting rules

  • Turn off ISR for your translated URLs. ISR ignores cookies, so it would cache Staging and editor responses.
  • A CDN in front of your site must not cache HTML for requests carrying the opemoxieoverride cookie.
  • Keep the Bablic script tag on every page, including the original-language pages.

Links

License

MIT