Installing from GitHub Packages
Folio's packages are published privately to GitHub Packages under the @nextgensoftwares scope (@nextgensoftwares/folio-model, @nextgensoftwares/folio-react, @nextgensoftwares/folio-reader, ...). Only repositories and people the organization grants access can install them. Every package ships ESM, CJS, type declarations, LICENSE and NOTICE. All Folio packages are released together with the same version, so pin them to one version.
1. Point the scope at GitHub Packages
Add an .npmrc next to your package.json (commit it; it holds no secret):
@nextgensoftwares:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}Only the @nextgensoftwares scope goes to GitHub; everything else still comes from npmjs. npm, pnpm and Yarn (v1, or v2+ with npmScopes in .yarnrc.yml) read this file.
2. Authenticate locally with a personal access token
- GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token.
- Select only the
read:packagesscope. If the organization uses SSO, click Configure SSO → Authorize for the organization afterwards. - Export it in your shell profile (never commit it):
export NODE_AUTH_TOKEN=ghp_xxx # read:packages only
pnpm add @nextgensoftwares/folio-model @nextgensoftwares/folio-layout @nextgensoftwares/folio-fontsFine-grained tokens do not support GitHub Packages' npm registry yet; use a classic token.
3. CI in another repository of the same organization
- In the organization, open each package (or the package's Package settings) → Manage Actions access → Add repository → pick the consuming repository with the Read role. Do this once per package you install (transitive Folio packages too, e.g.
folio-layoutwhen you installfolio-react). - In the workflow, grant the job
packages: readand passGITHUB_TOKEN:
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}No personal token is needed in CI once the repository has Actions access.
4. Docker builds: BuildKit secrets, never a baked token
Mount the .npmrc (or the token) as a BuildKit secret only for the install step, so it never lands in an image layer or the build cache:
# syntax=docker/dockerfile:1
FROM node:22-alpine AS deps
WORKDIR /app
RUN corepack enable
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
pnpm install --frozen-lockfileBuild with a user-level .npmrc that contains the real token:
printf '@nextgensoftwares:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=%s\n' "$NODE_AUTH_TOKEN" > /tmp/npmrc
docker build --secret id=npmrc,src=/tmp/npmrc -t my-app .
rm /tmp/npmrcIn GitHub Actions, docker/build-push-action takes the same secret: secret-files: npmrc=/tmp/npmrc (or secrets: npmrc=...). Do not use ARG NODE_AUTH_TOKEN or COPY .npmrc with a token in it: build args and copied files are visible in the image history. With Compose, declare it under build.secrets and a top-level secrets: npmrc: file: ....
5. Next.js (App Router)
- No
transpilePackagesneeded. The packages are compiled JavaScript (ES2022) withexportsmaps. Add them totranspilePackagesonly if you target older browsers than ES2022. - Client only for the editor and reader.
@nextgensoftwares/folio-react,@nextgensoftwares/folio-editorand@nextgensoftwares/folio-reader/reactuse the DOM: import them from'use client'components, and load the editor withnext/dynamic(() => import('./Editor'), { ssr: false })so it never renders on the server. - Fonts.
FontEngine(HarfBuzz WebAssembly) loads lazily in the browser. Serve the font files you lay out with (e.g. frompublic/fonts/) and register the same bytes withdocument.fonts(see Fonts). - No
resolveAliasformodule. HarfBuzz's own loader contains a Node-onlyimport("module")(plusrequire("fs")) that client bundlers try to resolve.@nextgensoftwares/folio-fontshas abrowserexport condition (0.3.0+) whose copy of that loader has the Node branch removed, so Turbopack and webpack client bundles build as is; server code keeps the Node entry. On 0.2.x, alias it away:turbopack: { resolveAlias: { module: { browser: './empty-module.ts' } } }innext.config.ts(an empty module) or upgrade. - Styles. Render
<FolioStyles />once, or import@nextgensoftwares/folio-react/styles.cssin a layout. - Workers. The reader's worker layout uses
new Worker(new URL('./layout.worker.ts', import.meta.url), { type: 'module' }), which both Turbopack and webpack bundle.
6. NestJS / Node services
Server-side packages (model, layout, fonts, docx, export-pdf, export-docx, sync, server-node, every plugin's /schema entry) work from both CommonJS (NestJS's default output) and ESM, on Node 20+:
import { readFileSync } from 'node:fs';
import { FontEngine } from '@nextgensoftwares/folio-fonts';
import { layoutDocument } from '@nextgensoftwares/folio-layout';
import { exportPdf } from '@nextgensoftwares/folio-export-pdf';
const geist = readFileSync('fonts/Geist-Variable.ttf');
const fonts = await FontEngine.create({ fallback: [] });
fonts.addFont({ family: 'Geist', data: geist });
const pdf = await exportPdf(layoutDocument(doc, { measurer: fonts }), { fonts: () => geist, shaper: fonts });@nextgensoftwares/folio-render-node needs the native @napi-rs/canvas; with Next.js route handlers, list it in serverExternalPackages. Never import a plugin's root entry on the server just for its schema: use @nextgensoftwares/folio-plugin-<name>/schema, which has no React or DOM.
Upgrading
All Folio packages share one version (Changesets "fixed" group). Upgrade them together, e.g. pnpm up "@nextgensoftwares/folio-*@0.2.0". Pre-1.0, a minor bump may break APIs; read the package changelogs.