Files
Daniil 21e936a827
dev / deploy (push) Successful in 2m15s
compute / deploy (push) Has been cancelled
chore: agentic upgrade
2026-05-17 02:11:33 +03:00

184 lines
5.5 KiB
Markdown

# Asset Handling
How to place static assets (images, icons, fonts, PDFs, stylesheets) inside an
FSD project. Assets follow the same placement rules as code: group by use
case, not by type, and keep them next to the code that uses them.
> **Caution:** A custom top-level `assets` segment that aggregates all static
> files is **not recommended**. It violates the FSD principles of high
> cohesion and locality of changes. Place assets where they are used.
---
## Decision Tree
1. **Used by exactly one slice?** Keep the asset inside that slice, usually
in the `ui/` segment, or in `model/` if it is part of business logic.
2. **Reused across the app (icons, placeholder images)?** Move to
`shared/ui/`.
3. **Global stylesheet, font, or app-level resource?** Place in the `app/`
layer (`app/styles/`, `app/fonts/`).
4. **Served as-is by the bundler (favicon, robots.txt)?** Use the framework's
`public/` folder. The `public/` folder is not part of FSD and does not
conflict with FSD layers.
---
## Slice-specific Assets
When an asset belongs to one page, widget, or feature, keep it inside that
slice. The asset lives next to the component that renders it:
```text
pages/
home/
ui/
hero-image.jpg ← Used only by HomePage
HomePage.tsx
index.ts
```
If a slice uses many static images, group them in a subfolder of `ui/`:
```text
pages/
home/
ui/
previews/
cake.jpg
pizza.jpg
sushi.jpg
HomePage.tsx
index.ts
```
### Non-UI Assets
Some assets are not part of the UI but are coupled to business logic. For
example, a PDF template used to generate invoices. Place these in the
`model/` segment alongside the logic that consumes them, not in `ui/`:
```text
features/
billing/
model/
invoice-template.pdf ← Coupled to create-invoice.ts
create-invoice.ts
index.ts
```
The principle is locality of changes: if you delete the slice, every file it
owns goes with it. An asset that lives in business logic should sit next to
that logic.
---
## Shared Assets
When the same asset appears across multiple slices, move it to `shared/ui/`.
Place reusable images in a topical subfolder, or place a single asset next to
the shared component that uses it:
```text
shared/
ui/
placeholders/ ← Reused placeholder images
cake.jpg
pizza.jpg
Dropdown.tsx
chevron.svg ← Used only by Dropdown, kept next to it
```
A single icon used by exactly one component in the UI kit stays next to that
component. A library of icons or images reused across many components goes
in a topical subfolder.
---
## Global Assets
Global stylesheets and fonts belong in the `app/` layer because they are
imported by the application entrypoint, not by individual slices:
```text
app/
styles/
reset.css
global.css
fonts/
inter.woff2
main.ts
```
Theme variables, CSS resets, and font registrations are app-wide concerns.
They bootstrap the application's visual layer the same way providers
bootstrap the runtime layer.
---
## Public Folder
Most bundlers expose a `public/` folder at the project root. Files here are
served as-is, without bundling or hashing.
- Vite, Next.js, Nuxt: `public/` at the project root.
- Astro: `public/` at the project root (path is fixed and cannot be changed).
`public/` is not part of FSD. It does not collide with FSD layers and does
not need to live under `src/`. Use it for files that must be served at fixed
URLs: favicon, `robots.txt`, `sitemap.xml`, OG images, and similar.
```text
public/
favicon.ico
robots.txt
og-image.png
src/
app/
pages/
shared/
```
Some projects keep a project-local `app/public/` folder when the bundler
allows assets to live alongside the entrypoint. Both layouts are valid.
---
## Summary Table
| Asset | Location |
| -------------------------------------- | ----------------------------------------- |
| Image used by one page/widget/feature | Inside the slice's `ui/` segment |
| PDF or template tied to business logic | Inside the slice's `model/` segment |
| Icon reused across the app | `shared/ui/` (topical subfolder if many) |
| Icon used by exactly one shared kit UI | Next to that component in `shared/ui/` |
| Global CSS reset, theme variables | `app/styles/` |
| Web fonts | `app/fonts/`, `public/`, or `app/public/` |
| Favicon, robots.txt, sitemap | `public/` (or `app/public/`) |
---
## Anti-patterns
- **Do not create a top-level `assets/` segment** that holds all images,
fonts, and icons. It breaks cohesion and forces consumers to import from a
folder unrelated to the code they are working on.
- **Do not extract a slice-local asset to `shared/` "in case" it gets
reused.** Move it only when actual reuse appears.
- **Do not place CSS modules in an `assets/` folder.** A component's
stylesheet belongs next to that component in `ui/`.
- **Do not name an FSD segment `public`.** The framework's `public/` folder
is reserved and lives outside `src/`.
- **Do not split assets and the components that use them.** A page that
ships a hero image should keep that image in the page so removing the page
removes the image.
---
## See Also
- `references/layer-structure.md`: segment rules and layer organization
- [Desegmentation](https://fsd.how/docs/guides/issues/desegmented/): why
technical-role grouping (including a generic `assets/` segment) hurts
cohesion