Bilingual (English / Persian) portfolio for Mina Soroush, metal engraving artist (Qalamzani / قلمزنی), working under the brand CHASING.
Persian is not a translation afterthought - every page ships in both languages and every layout has to work mirrored (RTL).
apps/
cms/ Strapi 5 - content, bilingual via the i18n plugin
web/ Next.js 16 - the public site
docs/ Design documents: the source of truth for the design phase
assets/ Artwork photography as delivered (see docs/05-assets.md)
docs/ is where decisions live. Read docs/README.md first.
- Node 26 (
.nvmrc) - Strapi 5 supports up to Node 26 - pnpm 10 -
corepack enableor install directly - Docker - runs Postgres for local development
pnpm install
cp apps/cms/.env.example apps/cms/.env # then fill in the secrets
pnpm db:up # Postgres in Docker
pnpm dev # both appsThen open http://localhost:1338/admin and create the first administrator. That account lives only in your local database; it is not shared.
| URL | |
|---|---|
| Strapi admin | http://localhost:1338/admin |
| Strapi API | http://localhost:1338/api |
| Next.js site | http://localhost:3000 |
Strapi's default port is 1337, but it is a commonly squatted port - Razer Synapse's
RzSDKServerholds127.0.0.1:1337on at least one dev machine. Windows lets both processes bind, andlocalhostsilently resolves to the other one, so Strapi appears to start and then answers426 Upgrade Required. We use 1338 to sidestep it. If a port ever behaves strangely, check withGet-NetTCPConnection -LocalPort <port> -State Listen.
| Command | Does |
|---|---|
pnpm dev |
Both apps in parallel |
pnpm dev:cms / pnpm dev:web |
One app |
pnpm build |
Build both |
pnpm db:up / pnpm db:down |
Start / stop Postgres |
pnpm db:logs |
Tail Postgres logs |
pnpm db:reset |
Destroys the database volume and starts fresh |
Compose runs Postgres only. Strapi and Next run on the host via pnpm - a
pnpm workspace's symlinked node_modules does not survive a bind mount
cleanly, and containerising the apps buys nothing for local work. Production
images are a separate decision, not yet made.
Database credentials default to chasing / chasing / chasing and are read
from the same DATABASE_* variables Strapi uses, so
docker-compose.yml and apps/cms/.env cannot drift apart
without failing loudly.
apps/cms/.env holds real secrets and is git-ignored. .env.example is the
contract - if you add a variable, add it there too, or the next clone (or
the next person) cannot start the project.
Generate a secret with:
node -e "console.log(require('crypto').randomBytes(16).toString('base64'))"