Audience configuration and lifecycle
This document explains how to add or update a documentation audience for ZAYAZ Docs
(e.g. client, developer, internal, or a new one like partner), and how those audiences connect to:
- Docusaurus (conditional content via
AudienceBlock) - Cloudflare Pages (separate deployments per audience)
- Cloudflare Access (optional password / SSO protection)
- The signed
manifest.json(per-audience build metadata)
An audience in this setup affects:
- What content is visible in MDX via
<AudienceBlock /> - Which Cloudflare Pages project / domain serves the docs
- Who can access it (public vs password / SSO via Cloudflare Access)
- Which
manifest.jsonthe MCP uses for that audience
Typical audiences today:
client– external customers (public or lightly protected)developer– engineers, vendors, technical partnersinternal– EcoWorld/ZAYAZ core team
You can extend this with new audiences like partner, auditor, etc., following this guide.
1. High-level architecture
Each audience is the combination of:
-
Docusaurus audience flag
- Configured in
docusaurus/docusaurus.config.jsvia environment variableDOCS_AUDIENCE. - Exposed in the site as
siteConfig.customFields.audience.
- Configured in
-
Audience-aware components in MDX
AudienceBlockreadssiteConfig.customFields.audienceand hides/shows content.- Example in an
.mdxfile:```mdx
<AudienceBlock audience={['internal','developer']}>
Internal + developer-only content
</AudienceBlock>
-
Cloudflare Pages projects
- Each audience has its own Pages project:
zayaz-docs-clientzayaz-docs-developerzayaz-docs-internal
- All projects point to the same GitHub repo/branch but run with different environment variables.
- Each audience has its own Pages project:
-
Cloudflare Access (optional)
- Public audiences (for example
client) can be left without Access protection. - Internal audiences use Cloudflare Access with SSO, email-based OTP, or group-based rules.
- Access configuration is documented in
security/cloudflare-access-mapping.md.
- Public audiences (for example
-
Signed manifest
- After a build, we generate
manifest.jsonfor that audience and sign it with GPG. - This allows external verifiers or MCP to confirm which pages exist for which audience and when they were built.
- After a build, we generate
2. Files and directories involved
When you add or modify an audience, you will typically touch:
-
docusaurus/docusaurus.config.js
Usesprocess.env.DOCS_AUDIENCEand exposescustomFields.audience. -
.github/workflows/portal.yml(or similar CI workflow)- Sets
DOCS_AUDIENCEfor the build. - Calls the Docusaurus build.
- Runs manifest generation + signing.
- Deploys to the correct Cloudflare Pages project.
- Sets
-
docusaurus/src/components/AudienceBlock.js- Implements audience-based filtering of MDX content.
-
MDX content files (for example
content/computation-hub/mice-micro-engines/pef-me.mdx)- Use
<AudienceBlock audience={['...']}>where appropriate.
- Use
-
Cloudflare Pages UI
- One Pages project per audience.
- Environment variables and domains are configured in the dashboard.
-
Cloudflare Zero Trust / Access UI
- Optional access rules and “password-like” protection for non-public audiences.
-
security/cloudflare-access-mapping.md- Documents which routes / hostnames are protected by which Access policies.
3. Quick checklist for adding a new audience
Assume the new audience is called partner.
-
Choose an audience ID
- Use a simple, lowercase string:
partner. - This ID will be used by:
DOCS_AUDIENCEenv varcustomFields.audienceAudienceBlockusage in MDX
- Use a simple, lowercase string:
-
Create a new Cloudflare Pages project
- Example project name:
zayaz-docs-partner. - Point it to the same GitHub repo and branch.
- Build command (example):
npm run build:partner
- Output directory:
docusaurus/build(or justbuildif you build from inside/docusaurus).
- Example project name:
-
Add a CI job for the new audience
- In
.github/workflows/portal.yml, add a job (or matrix entry) that:- Sets
DOCS_AUDIENCE=partner. - Builds the docs.
- Runs
generate-manifest.jswith--audience partner. - Signs
manifest.json. - Deploys to
zayaz-docs-partnerusing Cloudflare API (using secrets).
- Sets
- In
-
Configure secrets in GitHub
- Reuse:
CF_API_TOKENCF_ACCOUNT_ID
- Add:
CF_PAGES_PROJECT_PARTNER(the exact project name in Cloudflare Pages)
- If signing is enabled (recommended):
GPG_PRIVATE_KEYGPG_PASSPHRASE
- Reuse:
-
Configure Cloudflare Access (optional)
- If the
partnerdocs should be protected:- Create a Cloudflare Access application for the partner hostname (for example
partner-docs.zayaz.io). - Add rules:
- For example: emails ending with
@partnercompany.com, or a one-time passcode mechanism.
- For example: emails ending with
- Create a Cloudflare Access application for the partner hostname (for example
- If the docs should be public:
- Do not attach an Access app, or configure a rule that allows public access.
- If the
-
Use the audience in MDX
- Wherever you want partner-only content:
```mdx
<AudienceBlock audience={['partner']}>
Partner-only content goes here.
</AudienceBlock>
- Wherever you want partner-only content:
-
Update documentation
- Add an entry to
security/cloudflare-access-mapping.md(or another security README) describing:- Hostname for partner docs.
- Which Access policy applies.
- How to request access.
- Add an entry to
4. Docusaurus audience configuration
The audience is wired into Docusaurus via customFields.audience.
Example docusaurus/docusaurus.config.js fragment:
// docusaurus/docusaurus.config.js
// @ts-check
import {themes as prismThemes} from 'prism-react-renderer';
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'ZAYAZ Docs',
url: 'https://docs.zayaz.io',
baseUrl: '/',
favicon: 'img/favicon.ico',
i18n: { defaultLocale: 'en', locales: ['en'] },
// Audience injected via environment at build time
customFields: {
audience: process.env.DOCS_AUDIENCE || 'internal',
},
presets: [
[
'classic',
({
docs: {
path: '../content',
routeBasePath: '/',
editUrl: undefined,
showLastUpdateTime: true,
showLastUpdateAuthor: true,
include: ['**/*.md','**/*.mdx'],
},
blog: false,
theme: { customCss: require.resolve('./src/css/custom.css') },
})
]
],
themeConfig: {
navbar: {
title: 'ZAYAZ Docs',
items: [
{href: '/', label: 'Home', position: 'left'}
],
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
additionalLanguages: ['python','bash','yaml','json','sql']
}
}
};
export default config;
<AudienceBlock audience={['client']}>
This section is visible only in the client docs build.
</AudienceBlock>
5.2 Multiple audiences
<AudienceBlock audience={['internal', 'developer']}>
This section is visible in both internal and developer builds.
</AudienceBlock>
5.3 Nested audiences
You can nest blocks to progressively narrow visibility:
<AudienceBlock audience={['internal', 'developer']}>
### Internal Notes
<AudienceBlock audience={['developer']}>
Developer-only Deep Dive
</AudienceBlock>
</AudienceBlock>
6. CI workflow: building and signing per audience
Below is an illustrative fragment of a GitHub Actions workflow that:
- Generates registry JSON files
- Copies Excel assets
- Builds Docusaurus for a given audience
- Generates
manifest.json - Signs it with GPG
# .github/workflows/portal.yml (excerpt)
name: Build and deploy docs
on:
push:
branches: [ main ]
jobs:
build-and-deploy-client:
runs-on: ubuntu-latest
env:
DOCS_AUDIENCE: client
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
# Convert registries → JSON
- name: Generate Registry JSON
run: node scripts/generate-registry-json.js
- name: Generate Excel files JSON
run: node scripts/generate-excel-files-json.js
- name: Generate Signal Registry JSON
run: node scripts/generate-signal-registry-json.js
# Copy Excel files to Docusaurus static site
- name: Copy Excel files
run: |
mkdir -p docusaurus/static/excel
cp excel/*.xlsx docusaurus/static/excel/
# Build portal
- name: Build Docusaurus
run: |
cd docusaurus
npm ci
npm run build
# Generate manifest for this audience
- name: Generate manifest.json
run: |
node scripts/generate-manifest.js --audience client --buildDir build
# Install GnuPG
- name: Install GnuPG
run: sudo apt-get update && sudo apt-get install -y gnupg
# Import signing key
- name: Import signing key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
gpg --list-keys
# Sign and checksum manifest
- name: Sign and checksum manifest
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
cd docusaurus/build
sha256sum manifest.json > manifest.json.sha256
gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" \
-o manifest.json.sig --detach-sign manifest.json
# Deploy to Cloudflare Pages
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
projectName: ${{ secrets.CF_PAGES_PROJECT_CLIENT }}
directory: docusaurus/build
To add a new audience, you can:
- Clone this job as build-and-deploy-partner.
- Change:
- DOCS_AUDIENCE: partner
- --audience partner in the manifest step
- CF_PAGES_PROJECT_PARTNER in the deploy step
7. Password / access control per audience
Cloudflare Access is where you define “password-like” protection.
Typical patterns:
- client:
- Either public (no Access) or simple email-based OTP if you want light gating.
- developer:
- Restricted to specific email domains, GitHub org, or Okta/AAD groups.
- internal:
- Restricted to EcoWorld/ZAYAZ employees only.
Document the mapping in security/cloudflare-access-mapping.md, for example:
## Access mapping
- Audience: client
- Hostname: docs.zayaz.io
- Cloudflare Access: none (public)
- Audience: developer
- Hostname: dev-docs.zayaz.io
- Cloudflare Access: App "ZAYAZ Dev Docs"
- Rules:
- Identity provider: GitHub / Google Workspace
- Allowed groups: `zayaz-devs`, `trusted-vendors`
- Audience: internal
- Hostname: internal-docs.zayaz.io
- Cloudflare Access: App "ZAYAZ Internal Docs"
- Rules:
- Email domain: `@zayaz.io`, `@ecoworld.ai`
If you want something closer to a shared password, you can:
- Use “Service Auth” tokens or
- Configure a group in your IdP and only give group membership to people you share the “password” with.
- Use Cloudflare.com's Zero Trust access setup.
8. Updating an existing audience
When you update an audience (for example, change who can see it or how content is segmented):
- Content changes
- Update MDX files to move content in/out of
<AudienceBlock>wrappers. - Use audience tags consistently:
<AudienceBlock audience={['client','partner']}>
Shared external description.
</AudienceBlock>
- Access changes
- Update Cloudflare Access rules.
- Update security/cloudflare-access-mapping.md to reflect the new rules.
- Deployment changes
- If hostnames or Cloudflare Pages project names change:
- Update GitHub Action secrets (CF_PAGES_PROJECT_...).
- Update DNS records in Cloudflare.
- Validation
- Trigger a new build (push to main).
- Visit all audience URLs and confirm:
- Content visibility behaves as expected.
- Access rules behave as expected.
- manifest.json and manifest.json.sig exist in the built output.
9. Removing an audience
To decommission an audience:
- Remove its job from .github/workflows/portal.yml.
- Optionally remove the Cloudflare Pages project.
- Remove or update any MDX content that references that audience.
- Update security/cloudflare-access-mapping.md and any architectural diagrams.
- Remove related secrets from GitHub if no longer needed.
10. Summary
To add or change an audience, always think in terms of four layers:
- Build flag DOCS_AUDIENCE → customFields.audience.
- Content
<AudienceBlock audience={['...']}>in MDX. - Deployment Cloudflare Pages project + GitHub Action job.
- Access Cloudflare Access policies + documented mapping.
Follow the checklist in section 3 each time you introduce or significantly change an audience