Areca Docs

Pulling content at build time

Download the latest published snapshot with areca pull, understand the generated files, and keep the pull safe in your build.

Your site builds from a copy of the published content, not from live CMS requests. areca pull downloads the current published snapshot into JSON files in your project, and copies every image and file the content uses into your public folder. Your site then reads those files while it builds and while it serves pages, so neither content nor media depend on the CMS being up. See media.

Each pull also writes areca.gen.ts, a TypeScript module with the types of your content and a ready content object. That file is the exception to the rule above: commit it, so your editor and CI know the types. See typed content.

Run the pull before every build. The pulled JSON is created during the build, so it should be ignored by Git. areca init adds the default output and media directories to .gitignore for you, and keeps areca.gen.ts out of the ignored files.

Run the pull

pnpm exec areca pull
pnpm exec areca pull --out .cache/areca-content

The default output directory is src/generated/content. Use --out to choose another directory. The command reads ARECA_CMS_URL and ARECA_SITE_TOKEN, and it loads them from .env.local and .env if they are not already set. When it succeeds, it prints the publication ID, the snapshot hash and the number of entries in each collection:

Pulled publication <publication-id> (<snapshot-hash>): articles=12, authors=4
Media: 31 file(s) in public/areca (5 downloaded)
Types: src/generated/content/areca.gen.ts

The counts, the files and the code all use the camelCase names, such as blogPosts, even when your config key is blog-posts. The pull fails if the site has no published content yet, or if an entry does not match its schema. Publish content in the CMS, then run the pull again. See what the pull checks.

Choose where the types go

areca.gen.ts is written next to the pulled files by default. Use --types to write it elsewhere. The path is relative to the current directory:

pnpm exec areca pull --out src/generated/content --types src/generated/areca.gen.ts

The module's content object reads the folder you passed to --out, unless ARECA_CONTENT_DIR is set when the module loads. The CLI writes the folder as you typed it, so pass a relative path that is correct from the project root.

From code

If you need the pull in your own script, call pullContent from @areca-cms/site-sdk/pull. It returns the snapshot it wrote. Unlike the CLI, it only copies media when you pass assets:

import { pullContent } from '@areca-cms/site-sdk/pull'
import { resolve } from 'node:path'

const snapshot = await pullContent({
  outDir: 'src/generated/content',
  assets: { dir: resolve('public/areca'), publicPath: '/areca' },
})
console.log(snapshot.publicationId, snapshot.localAssets, snapshot.typesPath)

The options types (where to write areca.gen.ts, default <outDir>/areca.gen.ts) and contentDir (the folder written into the module, default outDir) control the generated module. See the client reference.

Add the pull to your build

Add the pull to the prebuild script, so it runs before every build:

package.json
{
  "scripts": {
    "prebuild": "areca pull --out src/generated/content",
    "build": "vite build"
  }
}

With this setup, pnpm build pulls the latest content first. In CI, set ARECA_CMS_URL and ARECA_SITE_TOKEN as secrets for the build job. The site token is a read token, so it is the only token a build needs. See installation.

Output layout

Each pull writes one JSON file for each collection into a new folder under .publications, then points manifest.json at it. Media goes to the media folder, and the types go to areca.gen.ts.

hero-banner-3f2a91c0.jpg
areca.gen.ts
manifest.json
blogPosts.json
authors.json
.assets.json

Each collection file, such as blogPosts.json, is an array of entries. The file is named after the collection's camelCase key, so blog-posts becomes blogPosts.json. Each entry has the entry's id, slug, publishedAt (an ISO timestamp, or null), its collection, its data and, when the CMS sends it, its seo block. The keys of data are camelCase too. Your site reads these entries with content, which follows manifest.json to the right folder.

areca.gen.ts describes the schema of the publication, and it exports the content object. Commit it. Typed content explains the names and the checks.

manifest.json

manifest.json describes the snapshot. Read it to find out which publication your build used.

FieldDescription
publicationIdThe ID of the publication the snapshot came from.
hashA hash of the snapshot contents. The same content gives the same hash.
createdAtWhen the snapshot was created.
collectionsAn object that maps each collection's camelCase key to its number of entries.
generationThe folder under .publications that holds this snapshot.
assets{ publicPath, count } when media was copied locally.

The collections object is also the list of collections in the snapshot. Use its keys, not the directory listing, to find out which collections are current.

areca.gen.ts is not listed here. It describes the schema rather than one pull, and it is rewritten on every pull after the content has been switched.

Media

By default, the pull copies every image and file the content references (image fields, SEO images, images and file links in rich text) into public/areca, and rewrites the content to point at the copies. Your site then serves them like any other static file, and visitors never request anything from the CMS.

  • Image fields get url: "/areca/hero-banner-3f2a91c0.jpg", so <img src={image.url}> works unchanged.
  • Rich text gets the same paths in json, markdown, document and html.
  • SEO data (og:image, twitter:image, JSON-LD) gets absolute URLs built from the site's production URL in the CMS, for example https://www.acme.com/areca/hero-banner-3f2a91c0.jpg, because social networks and search engines need absolute image URLs.

File names are the original file name as a slug plus part of the file's unique ID. A file is downloaded once; later pulls skip files that are already there.

Configure it with assets in areca.config.ts. areca init asks for these values and writes them for you:

areca.config.ts
export default defineSiteContent({
  siteKey: 'acme-site',
  assets: {
    download: true, // false keeps CMS URLs
    dir: 'public/areca', // relative to the project root
    // publicPath: '/areca', // needed only when dir is not under public/
  },
  collections: { /* ... */ },
})

A folder under public/ is served from the matching path: public/areca from /areca, public/media/cms from /media/cms. For other folders, for example static/ in SvelteKit, set publicPath to the URL path your framework serves it from. Without an assets block, the defaults apply. The assets settings stay in your project: areca manifest push does not send them, and areca manifest pull keeps them.

The folder must be inside the project: absolute paths and paths that go above the project root are refused. Media files are downloaded from ARECA_CMS_URL, with no token. Each download goes to a temporary file first, and the media folder only changes once all of them succeed. If a file fails to download, the pull fails, and the previous content and media stay exactly as they were.

Snapshot pinning

A pull downloads the snapshot that the CMS gives your site at that moment. The CMS chooses which publication that is:

  • While Areca is building or deploying a release, the snapshot endpoint returns that release's publication to every caller with a valid site token. The build gets the same content as the release, and so does a pull made during the deployment.
  • Otherwise, it returns the live publication. A pull on your laptop, with no release in progress, gets this one.

To pull an exact publication, pass --publication <id> or set ARECA_SNAPSHOT_ID. An empty value or 0 selects the live publication.

Areca's own deployment integration sets ARECA_SNAPSHOT_ID as a Docker build argument, and it changes on every build. Docker then cannot reuse a cached content pull from an earlier build, even when the code and CMS settings are the same. If you build with Docker, declare the argument before the build step that runs your prebuild pull:

Dockerfile
ARG ARECA_SNAPSHOT_ID=
ENV ARECA_SNAPSHOT_ID=$ARECA_SNAPSHOT_ID
RUN --mount=type=secret,id=ARECA_SITE_TOKEN,required=true \
    ARECA_SITE_TOKEN="$(cat /run/secrets/ARECA_SITE_TOKEN)" \
    pnpm build

Mount the site token as a BuildKit secret. Do not pass it as a plain ARG or ENV, because those values are stored in the image.

Re-pulling

Each pull writes the new snapshot into a fresh folder and then switches manifest.json to it in one rename. If a pull fails, manifest.json still points at the previous complete snapshot, and its media files are untouched.

The current folder and the two before it are kept, so a server that read an older manifest.json keeps working. Older folders are deleted, together with media files that only they used. Files in the media folder that no pull recorded, such as files you added yourself, are never deleted.

Running commands with env files

areca run runs any command with .env.local and .env loaded first. Use it when your dev server or build tool does not load env files itself:

pnpm exec areca run -- vite dev --port 3001

The command after -- is joined with spaces and run through the shell. Pass the whole command as one string if it contains quoted arguments, because the shell sees the joined text and not your original quoting:

pnpm exec areca run -- "node scripts/build.mjs --label 'site build'"

areca run exits with the exit code of the command it runs.

Safety

The pull has several built-in checks. You do not need to add them yourself, but they affect what you can do:

  • Collection keys are checked. A key that breaks the identifier rules stops the pull with an Unsafe collection or field identifier error.
  • Names must be unique in code. Keys that map to the same camelCase name, and collection names that are reserved (resolve, href, preview, globals or previewGlobals), stop the pull. See typed content.
  • The content must match its schema. Every entry is checked before anything is written. See what the pull checks.
  • Files must stay in the output directory. The pull refuses any path that would be written outside the output directory.
  • Symbolic links are refused. The pull does not write to a file that is a symlink, and it fails with Content output must not be a symbolic link. readContent refuses symlinked files too.
  • Output directories are created as needed. The pull creates the output directory if it does not exist.

Keep the output directory inside your project, and do not commit the pulled JSON. Pulled content is build input, and a fresh pull replaces it. Do commit areca.gen.ts.

Troubleshooting

MessageWhat to do
No publication exists for this sitePublish content in the CMS, then run the pull again.
Missing ARECA_CMS_URL or Missing ARECA_SITE_TOKENSet the variables in the environment, or in .env.local in the directory where you run the command.
Failed to reach CMSCheck that ARECA_CMS_URL is correct and that the CMS is running.
401The site token is wrong, or it was rotated. Update ARECA_SITE_TOKEN.
Publication does not match its schema:An entry breaks its schema. The lines list <collection>/<slug>.<field>: <problem>. Fix the entry in the CMS, or fix the schema, then publish and pull again. Nothing was changed.
Collection keys ... both map to "<name>" or Field keys ... both map to "<name>"Two keys have the same camelCase name. Rename one of them in areca.config.ts, push, and publish.
Collection "<name>" uses the reserved nameA collection cannot be called resolve, href, preview, globals or previewGlobals. Rename it.
Warning: Publication <id> has no embedded schemaThe publication was made before schemas were embedded, so the types use the current schema. Go live again to embed it.
Failed to download N media file(s)The listed files could not be fetched from ARECA_CMS_URL. Check that the CMS serves /api/assets/file/ from that address, then run the pull again. Nothing was changed.
Pulled, but cleaning up older content failedThe new content is live. Removing an older generation or its media failed, usually because of file permissions. Fix the permissions; the next pull cleans up again.
assets.dir must be a folder inside the projectUse a path relative to the project root that stays inside it, such as public/areca.
Cannot tell which URL serves <dir>The media folder is not under public/. Set assets.publicPath in areca.config.ts.
This CMS version does not list media filesThe CMS is older than the SDK. Update the CMS; until then media URLs point at the CMS.

See errors and limits for the full list.

On this page