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

@miyamo2/astro-algolia-index

v0.1.0

Published

Astro integration that pushes your build's records to an Algolia index, with an atomic replace-all reindex and a build-time record sink

Readme

@miyamo2/astro-algolia-index

npm license

Astro integration that pushes your build's records to an Algolia index.

Astro has no equivalent of gatsby-plugin-algolia: algoliasearch is a client, and the Algolia crawler works from the outside in. This fills the gap — you build records where your content already lives, and the integration ships them to Algolia when the build finishes.

Features

  • Atomic reindexing. replaceAllObjects swaps through a temporary index, so records that disappeared from your build disappear from the index too — in one step, with no window where search is empty.
  • A record sink, not a callback. Records are usually only assemblable while pages render (getCollection(), rendered HTML, resolved image URLs), but they can only be shipped once the build is done. writeRecords() bridges the two without you hard-coding a path anywhere.
  • Settings that merge. Algolia's setSettings is a PUT — it resets anything you leave out. The default settingsMode: "merge" reads the current settings first, so what you configured in the dashboard survives.
  • Fails soft by default. Missing credentials or a missing records file warn and let the build through; onError: "error" turns them into build failures.
  • Dry run. ALGOLIA_DRY_RUN=true logs the record count without calling the API.

Requirements

  • Astro >=5.0.0
  • Node.js 18+ (or Bun)

Installation

npx astro add @miyamo2/astro-algolia-index

Or manually:

npm install @miyamo2/astro-algolia-index
# pnpm add / yarn add / bun add

algoliasearch comes along as a dependency — you do not need to install it separately. It is loaded lazily, so a dry run or a build without credentials never pays for it.

Usage

1. Register the integration

// astro.config.ts
import { defineConfig } from "astro/config";
import algoliaIndex from "@miyamo2/astro-algolia-index";

export default defineConfig({
  integrations: [
    algoliaIndex({
      appId: process.env.PUBLIC_ALGOLIA_APP_ID,
      apiKey: process.env.ALGOLIA_ADMIN_KEY,
      indexName: process.env.PUBLIC_ALGOLIA_INDEX_NAME,
      settings: {
        searchableAttributes: ["title", "content", "tags"],
        attributesToSnippet: ["content:10"],
      },
    }),
  ],
});

Every credential is optional — leave them out and the environment variables listed under Options are used instead.

2. Write records while your pages render

// src/lib/content.ts
import { getCollection } from "astro:content";
import { writeRecords } from "@miyamo2/astro-algolia-index/records";

const articles = await getCollection("articles");

await writeRecords(
  articles.map((article) => ({
    id: article.id,
    title: article.data.title,
    content: article.body,
    tags: article.data.tags,
    url: `https://example.com/articles/${article.id}`,
  })),
);

That is the whole integration surface. objectID is derived from id by default, and the records are pushed once the build finishes.

Other record sources

records accepts three shapes when the sink does not fit:

// a fixed array
algoliaIndex({ records: [{ id: "a", title: "A" }] })

// a callback, run during astro:build:done
algoliaIndex({ records: async ({ dir, config, logger }) => [...] })

// a specific file (NDJSON or a JSON array), relative to the project root
algoliaIndex({ records: { file: "dist/search-index.json" } })

Options

| Option | Type | Default | Description | | --- | --- | --- | --- | | appId | string | $ALGOLIA_APP_ID ?? $PUBLIC_ALGOLIA_APP_ID | Algolia application ID. | | apiKey | string | $ALGOLIA_ADMIN_KEY ?? $ALGOLIA_API_KEY | Admin API key — needs write access. | | indexName | string | $ALGOLIA_INDEX_NAME ?? $PUBLIC_ALGOLIA_INDEX_NAME | Target index. | | records | AlgoliaRecord[] \| (ctx) => AlgoliaRecord[] \| { file } | the shared sink | Where records come from. | | objectIDField | string | "id" | Field to derive objectID from. A record that already has objectID keeps it. | | transform | (records) => records | — | Runs on the whole array right before indexing. Filtering, chunking, whatever. | | settings | IndexSettings | — | Omit and the settings API is never called. | | settingsMode | "merge" \| "replace" | "merge" | "merge" reads current settings first; "replace" sends exactly what you passed. | | forwardToReplicas | boolean | false | Forward settings to replica indices. | | mode | "replaceAll" \| "saveObjects" \| "partialUpdate" | "replaceAll" | See Indexing modes. | | batchSize | number | 1000 | Records per batch request. | | waitForTasks | boolean | false | Wait for indexing tasks. Ignored for replaceAll, which always waits. | | createIfNotExists | boolean | true | partialUpdate only: create records that do not exist yet. | | dryRun | boolean | $ALGOLIA_DRY_RUN is set to anything but "" or "false" | Log the count, call no API. | | enabled | boolean | true | false registers no hooks at all. | | onError | "warn" \| "error" | "warn" | See Error handling. | | clientOptions | AlgoliaClientOptions | — | hosts / requester / timeouts, passed straight to algoliasearch. |

An option always wins over its environment variable. Invalid options throw immediately, listing every problem, rather than failing at the end of the build.

Indexing modes

| Mode | What it does | Deletes? | | --- | --- | --- | | replaceAll | Atomic swap through a temporary index. | Yes — records missing from the build are dropped. | | saveObjects | Adds and overwrites the records you pass. | No | | partialUpdate | Updates only the attributes present in each record. | No |

How it works

Records can only be assembled while pages render, but they can only be shipped once the build is done. The two halves agree on a file path through an environment variable, which is why nothing in your code names that path:

astro.config.ts                        src/lib/content.ts
  algoliaIndex({...})                    import { writeRecords } from
    │                                      "@miyamo2/astro-algolia-index/records"
    │ astro:config:setup                   │
    ├─ resolve(config.cacheDir) ─────────► │  (same process, via process.env)
    │  ASTRO_ALGOLIA_RECORDS_FILE          │
    │                                      │  while pages render
    │                                      ├─ writeRecords(records)
    │                                      │    → NDJSON, one record per line
    │ astro:build:done                     │
    ├─ read the file                       │
    ├─ transform / derive objectIDs        │
    ├─ setSettings   (merge by default)    │
    └─ replaceAllObjects                   │
  • The sink lives under your Astro cacheDir (node_modules/.astro/ by default), so it is already ignored by git.
  • The first writeRecords() call in a process truncates the file; later calls append. So a previous build's records never leak into this one, and astro dev does not grow the file without bound. appendRecords() is the never-truncating variant.
  • The reader also accepts a plain JSON array, so a records file produced by another tool works with records: { file: "..." }.
  • setSettings runs before replaceAllObjects on purpose: that helper copies settings, rules and synonyms into its temporary index before moving it back, so this ordering is what keeps your settings across the swap.

Sink API

import {
  writeRecords,
  appendRecords,
  recordsFilePath,
} from "@miyamo2/astro-algolia-index/records";

| Export | Description | | --- | --- | | writeRecords(records, { file? }) | Truncates on the first call in a process, appends after. Returns the path written. | | appendRecords(records, { file? }) | Always appends. Does not affect writeRecords's first-call bookkeeping — pick one per build, or call writeRecords first. | | recordsFilePath() | The resolved sink path for this process. |

This entry point pulls in node:fs and node:path and nothing else — importing it from a page never drags algoliasearch into your module graph.

Error handling

onError decides what happens when credentials are missing, the records file is absent or malformed, or the Algolia API rejects the write:

  • "warn" (default) — log a warning, skip indexing, let the build succeed. Good for preview deploys and contributors without credentials.
  • "error" — throw and fail the build. Good for production deploys where a silently unindexed release is worse than a red build.

Security

Never put your admin API key in a PUBLIC_-prefixed variable. Astro inlines PUBLIC_* values into the client bundle, so a PUBLIC_ALGOLIA_ADMIN_KEY ends up readable by anyone who views your site's source — and an Algolia admin key can rewrite and delete your indices.

That is why the environment fallbacks are asymmetric:

| Value | Public? | Fallbacks | | --- | --- | --- | | appId | yes | ALGOLIA_APP_ID, PUBLIC_ALGOLIA_APP_ID | | indexName | yes | ALGOLIA_INDEX_NAME, PUBLIC_ALGOLIA_INDEX_NAME | | apiKey | no | ALGOLIA_ADMIN_KEY, ALGOLIA_API_KEY — no PUBLIC_ variant, ever |

For the search UI in your pages, use a search-only key instead — that one is safe to expose.

Migrating from gatsby-plugin-algolia

| gatsby-plugin-algolia | here | | --- | --- | | queries: [{ query, transformer, indexName }] | Build the records yourself and hand them over with writeRecords(). One index per integration instance in v0.1. | | settings | settings | | mergeSettings: true | settingsMode: "merge" (the default) | | mergeSettings: false | settingsMode: "replace" | | enablePartialUpdates: true | mode: "partialUpdate" | | matchFields | not implemented — see Roadmap | | chunkSize | batchSize | | dryRun | dryRun / ALGOLIA_DRY_RUN | | continueOnFailure: true | onError: "warn" (the default) | | skipIndexing | enabled: false |

Note that gatsby-plugin-algolia's mergeSettings: true is what this package does by default, and it is not what a bare setSettings call does — that one resets every attribute you left out.

Roadmap

  • indices: IndexConfig[] for multiple indices in one integration (the queries equivalent)
  • Automatic chunking for Algolia's 10 KB record size limit
  • Digest-based incremental indexing (enablePartialUpdates' diffing)

License

MIT