Keep DB seed sources outside the code tree
Keep DB seed sources outside the code tree
When a project ships content (Markdown, JSON, CSV…) as DB seed, where those files live is a surprisingly big decision. The obvious place — inside the app folder that consumes them (frontend/myapp/content/) — turns out to be the worst long-term home. Putting them at monorepo root is almost always right.
1. What goes wrong inside the app folder
| Incident | Root cause |
|---|---|
| Seed walker scans both v1 and v2 folders → duplicate inserts | Migration source and SSOT cohabit |
| Seed files end up in the app image → larger container | Bundled with app code |
| Editing seed triggers an app rebuild | Next/Vite watch content/ |
| docker-compose mount points into the app dir → confuses operators | Blurs content ≠ code |
2. Why monorepo-root placement wins
my-monorepo/
├── notes/ ← seed SSOT (monorepo root)
│ ├── backend/
│ │ ├── 01-...ko.md
│ │ └── 01-...en.md
│ └── frontend/...
├── courses/ ← seed SSOT
│ └── <series>/...
├── frontend/
│ └── admin/ ← app (seeder walks ../../notes)
└── docker-compose.yml (../../notes:/notes:ro mount)
At root:
- Folder structure itself enforces content ≠ code
- docker mounts read cleanly: content =
/notes, code =/app - Editing seed triggers zero app rebuilds
- After v1 → v2 migration, you can simply delete the v1 folder without touching app code
3. Clean up dead paths after migration — in one shot
The most common slip when adopting this pattern is leaving the v1 folder around "just in case." If you do:
- Seeder walks v1 + v2 → duplicates or gaps
- New contributors can't tell which is SSOT
- The v1 mount in
docker-compose.ymlbecomes a stale dead mount
The right fix is in one go: git rm -rf + delete the mount line + delete helper functions + sync 5 docs. Miss any one of those five spots and the next contributor gets confused.
4. Before / after
| Aspect | Inside app folder | At monorepo root |
|---|---|---|
| Content ↔ code boundary | Blurry | Folder-level separation |
| Build trigger on edit | Triggered | Not triggered |
| Container image size | Includes seeds | Seeds mounted at runtime |
| Cleanup after v1 → v2 | Easy to leave behind | One-shot git rm |
| SSOT clarity for new joiners | "Which one?" | Root is explicit |
5. Adopting this in your own project
- If content lives in
app/content/orsrc/seed/, move it to monorepo root. - Update the seeder to walk relative to
process.cwd() + ../../. - Unify docker-compose mounts to the form
../../notes:/notes:ro. - When v1 → v2 migration is complete, delete v1 folder + mount + helper in one commit.
- State "content SSOT = root
/notes/courses" in README and folder-structure docs.
Code and content are assets with different change cadences. Things that change on different cadences should live in different folders — that's the lowest operational cost.