Configuration files
Configuration rules, shared types, and a file index for the Saavo template.
Project configuration lives in config/. To change site information, feature switches, products, payments, or third-party services, first find the relevant configuration file, then check the schema and calling code for field requirements.
Do not store secrets in configuration files
Content in config/ is committed to Git and may also appear in client bundles or build artifacts. Passwords, API keys, access tokens, and signing secrets must be stored in environment variables or Cloudflare secrets.
Configuration files can contain only static values
Configuration settings accept only static values such as strings, numbers, booleans, arrays, and plain objects. Do not put functions, class instances, Promises, or values that require runtime computation in configuration files. Constant expressions such as 5 * 1024 * 1024, whose results are known at load time, are allowed. Logic that depends on the current request, user, database, system time, or random numbers belongs in the corresponding business code.
Reading configuration
Files in config/ are ordinary TypeScript modules. The application imports configuration constants directly, without converting them to JSON. Most configuration objects end with as const satisfies ...Input:
as constpreserves literals such as product IDs and role names so that other code can infer precise union types from them.satisfieschecks field names and input types while preserving the object's exact type.
import type { WebsiteDeployCfgInput } from '@/libs/config/schemas/deploy';
export const websiteDeployCfg = {
// Configuration settings
} as const satisfies WebsiteDeployCfgInput;The server reads the full configuration through:
getServerConfig(): AppConfigOn the first call, the application merges all configuration and validates it with AppConfigSchema. Once validation succeeds, the result is stored in the configuration registry, and subsequent calls read it directly. If validation fails, the application reports Configuration validation failed at startup and stops startup.
The browser reads public configuration through:
getClientConfig(): typeof _rawConfigClient configuration currently includes only base, i18n, scopes, ads, and analytics. These values are bundled for the browser and are directly visible to visitors, so they must not contain sensitive information.
Type checking and startup validation are different
The satisfies operator checks only constraints that TypeScript can express. Zod checks email formats, numeric ranges, time interval formats, and references between product and affiliate configuration at startup. After changing configuration, checking only for editor errors is not enough.
Types ending in ...Input are the input types used in configuration files, usually with satisfies. A type with the same name but without Input generally represents the result after Zod validation. The two types may differ when fields have defaults, preprocessing rules, or optional settings. Use the Input type when writing configuration. Business code reads the validated configuration through getServerConfig().
Shared types
Multiple configuration files use LocalizedText and DateIntervalType. Their fields and usage rules are described below.
LocalizedText
The LocalizedText type specifies localized text and is defined in src/libs/i18n/types.ts. It is an object with value and key fields, not a plain string.
Prop
Type
Provide at least one of value or key. For multilingual text, you usually provide both: the page first looks up the current language's text using key, then displays value if no translation is found.
{
value: 'Saavo Starter',
key: 'website.title',
}DateIntervalType
The DateIntervalType type represents a duration as a string. It is commonly used for session lifetimes, cache durations, rate-limit periods, and affiliate settlement periods. The format is a number followed by a time unit, with an optional space between them.
| Unit | Meaning | Example |
|---|---|---|
ms | Milliseconds | 500ms |
s | Seconds | 30s |
m | Minutes | 15m |
h | Hours | 2h |
d | Days | 7d |
w | Weeks | 2w |
mo | Months | 1mo |
y | Years | 1y |
You can also write units as full English words such as seconds, minutes, and hours. A number without a unit is interpreted as milliseconds, which is easy to get wrong, so always specifying a unit is recommended. Note that m means minutes, while mo means months.
Changing configuration
Find the file responsible for the feature
Open the relevant article from the table below and check the field path, type, template default, and purpose. Do not put a setting in an unrelated file just because field names look similar.
Follow the existing structure
Keep the exported variable names and the final satisfies type constraint. For new object keys, prefer stable, readable English identifiers. Use LocalizedText for user-facing text rather than using Chinese text directly as a product ID, role name, or authorization scope.
Update related resources
When changing translation keys, update the JSON files for every supported language. When changing Stripe Price IDs, Cloudflare resource names, email addresses, or third-party service identifiers, also confirm on the relevant platform that the resources exist and that the account you use can access them.
Run the full checks
Run npm run lint, npx tsc --noEmit, and npm run build in order. The production build triggers content generation and the application build, helping catch configuration errors that appear only during startup or bundling.
Configuration file index
| File | Purpose |
|---|---|
base.ts | Site name, SEO sharing image, social accounts, administrator emails, and email addresses |
i18n.ts | Supported and default site languages |
deploy.ts | Hosts, security policies, storage, email, authentication, analytics, and content switches |
payment.ts | Payment provider, Checkout, and billing portal |
products.ts | Products, plans, Stripe Price IDs, and roles and entitlements granted after purchase |
capability.ts | Capability definitions used by roles and entitlements |
roles.ts | Roles and their static capabilities |
entitlements.ts | Entitlements and the capabilities they include |
scopes.ts | OAuth 2.0 authorization scopes |
upgrade.ts | Plan upgrade suggestions on the OAuth authorization page, empty by default |
affiliate.ts | Affiliate program, commission, and settlement rules |
analytics.ts | Third-party analytics services |
notifications.ts | External notification channels |
cookie-consent.ts | Cookie consent modes, categories, and third-party services |
header-menu.ts | Header navigation |
footer-menu.ts | Footer navigation |
ads.ts | Browser-side configuration for advertising services |
At startup, the application validates configuration with the Zod schemas in src/libs/config/schemas/. TypeScript type checking catches field name and type errors. Zod also checks email formats, numeric ranges, cross-file references, and other constraints.
General considerations
- Do not casually rename exported configuration variables. The
src/libs/config/client.tsandsrc/libs/config/index.tsmodules import them directly, so renaming them also requires updating the loading code. - The
base.ts,i18n.ts,scopes.ts,ads.ts, andanalytics.tsfiles are included in client configuration. Treat every field as public information. Other configuration files are also committed to the repository and must not contain secrets either. - Configuration files reference one another. Roles reference capabilities, products reference roles and entitlements, affiliate configuration references products and plans, and upgrade suggestions reference OAuth authorization scopes. Search the entire project before renaming or deleting definitions rather than changing only the definition itself.
- TypeScript and Zod cannot confirm that external resources exist. Check Stripe Price IDs, Cloudflare bindings, email domains, and third-party service accounts on their respective platforms.
- Do not hardcode environment differences in configuration files. Domains, secrets, and values that differ between development, preview, and production belong in environment variables, Cloudflare secrets, or the corresponding deployment configuration.
- Pay attention to order when changing default arrays or objects. Some pages display menus, products, and options in configuration order. Reordering them can change what users see even when there is no type error.