This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- @vitejs/plugin-react uses Babel for Fast Refresh
- @vitejs/plugin-react-swc uses SWC for Fast Refresh
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
export default tseslint.config({
extends: [
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})Backend persistence supports two drivers:
file: JSON files inserver/data(fallback)postgres: PostgreSQL with SQL migrations
Environment variables:
RW_STORAGE_DRIVER=auto|file|postgres(default:auto)DATABASE_URL=postgres://...(required forpostgres)RW_MIGRATIONS_DIR=server/migrations(optional)RW_FEED_SCHEDULER_ENABLED=true|false(optional)RW_BACKUP_SCHEDULER_ENABLED=true|false(optional)RW_FEED_FETCH_TIMEOUT_MS(optional, default:20000)RW_FEED_FETCH_MAX_BYTES(optional, default:20971520)RW_FEED_FETCH_MAX_REDIRECTS(optional, default:5)RW_FEED_MAX_ROWS(optional, default:50000)RW_FEED_FETCH_ALLOW_PRIVATE_HOSTS=true|false(optional, default:false; keepfalsein production)RW_FEED_FETCH_ALLOWED_HOSTS=host1,host2(optional host allowlist for URL imports)RW_PG_BOOTSTRAP_FROM_LOCAL=true|false(optional, default:false)RW_SEED_ENABLED=true|false(optional, default:trueoutside production,falsein production)RW_ALLOW_FILE_STORAGE_IN_PROD=true|false(optional, default:false; safety override)RW_MEDIA_STORAGE_DRIVER=auto|local|s3(default:auto)RW_RUN_DB_MIGRATIONS_ON_START=true|false(optional, default:truein Docker entrypoint)
Media storage (/api/admin/upload):
localmode stores files inUPLOADS_DIRand serves them from/uploads/*.s3mode uploads files to an S3-compatible bucket and returns CDN/public URL.automode usess3whenRW_S3_BUCKETis set, otherwiselocal.- If S3 upload fails at runtime, upload automatically falls back to
localmode (no manual switch needed).
S3/CDN variables:
RW_S3_BUCKET(required fors3)RW_S3_REGION(default:us-east-1)RW_S3_ENDPOINT(optional, for S3-compatible providers)RW_S3_FORCE_PATH_STYLE=true|false(optional)RW_S3_ACCESS_KEY_ID,RW_S3_SECRET_ACCESS_KEY(optional, IAM/default chain is supported)RW_S3_PREFIX(optional, default:media)RW_MEDIA_CDN_BASE_URLorRW_S3_PUBLIC_BASE_URL(optional public/CDN base URL)RW_MEDIA_CACHE_CONTROL(optional, default:public, max-age=31536000, immutable)
Feed scheduler behavior:
- If
RW_FEED_SCHEDULER_ENABLEDis set, its value is used directly. - If not set, scheduler defaults to
truein long-running Node runtime andfalsein serverless runtime. - Auto-refresh updates only
draft; publish topublishedremains manual via admin publish action. - Missing feed records now follow lifecycle
active -> hidden -> archivedon consecutive imports. - URL import/fetch path is guarded by timeout, max-bytes, max-redirects, max-rows, and host policy (private/local hosts blocked by default).
Backup scheduler behavior:
- If
RW_BACKUP_SCHEDULER_ENABLEDis set, its value is used directly. - If not set, scheduler defaults to
truein long-running Node runtime andfalsein serverless runtime. - Scheduler creates one automatic backup per day and keeps only the latest 3 automatic backups.
- In admin (
/admin/backups) you can create manual backups; manual backups are kept until deleted. - Restore from backup updates
draftcontent only. Publish topublishedremains manual. - In admin leads (
/admin/leads) you can separately restore lead processing fields (status,assignee,admin_note) from a backup snapshot; new leads are never deleted by this operation.
Safe deployment notes:
- For production, set
RW_STORAGE_DRIVER=postgresexplicitly (avoid silent fallback to file mode). - In production, startup is blocked when storage driver is
fileunlessRW_ALLOW_FILE_STORAGE_IN_PROD=trueis explicitly set. - Keep a stable external
DATABASE_URLbetween deploys. - Local JSON -> PostgreSQL bootstrap is disabled by default and runs only when
RW_PG_BOOTSTRAP_FROM_LOCAL=true. - Demo seed is disabled in production by default (
RW_SEED_ENABLEDcan override). - Docker container startup runs
npm run db:migratebefore app start (disable only withRW_RUN_DB_MIGRATIONS_ON_START=false).
Run migrations:
npm run db:migratePostgreSQL initialization and end-to-end smoke (PowerShell):
$env:RW_STORAGE_DRIVER = 'postgres'
$env:DATABASE_URL = 'postgres://user:password@127.0.0.1:5432/dbname'
npm run db:migrate
npm run smoke:postgres:e2eFeed import path smoke (PostgreSQL):
DATABASE_URL=postgres://user:password@127.0.0.1:5432/dbname npm run smoke:feed-import:e2eBackup system end-to-end smoke:
npm run smoke:backup:e2eBackup smoke on PostgreSQL:
SMOKE_STORAGE_DRIVER=postgres RW_STORAGE_DRIVER=postgres DATABASE_URL=postgres://user:password@127.0.0.1:5432/dbname npm run smoke:backup:e2eYou can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default tseslint.config({
extends: [
// other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})