# Assets
Images, fonts, PDFs, downloads. Any file your app ships that is not code.
There are two kinds, and they are not the same problem:
- **Static assets** live in your source tree. You know their names when you
write the code. They are content-hashed at build time and served from
`/assets/`.
- **Dynamic assets** come from the database: a product photo, an avatar, an
upload. You do not know the filename until the row is read, so a build-time
URL cannot exist. These are served by a route.
## Static assets
Put them anywhere under `app/`. The scaffold uses `app/shared/assets/`.
```
app/shared/assets/
favicons/favicon.svg
products/quad-lineup.jpg
```
### In markup, write a path
```html
```
Relative paths only. The build rewrites the attribute to the file's
content-hashed URL:
```html
```
This works on `src`, `href`, `poster` and `data`, and on the `content` of the
meta tags that hold URLs (`og:image`, `twitter:image`, and the rest of that
family). `content` is a generic attribute name, so the URL-bearing properties
are a fixed list rather than anything inferred.
It also works in CSS:
```css
.hero { background-image: url("../assets/products/quad-lineup.jpg"); }
```
### In code, import it
An import of a non-code file gives you its URL as a `string`:
```ts
import favicon from "#app/shared/assets/favicons/favicon.svg";
favicon; // "/assets/app/shared/assets/favicons/favicon.f9e9effe4ae761bd.svg"
```
An import is the only way to get an asset URL into a value. You need one the
moment the asset is part of data rather than one fixed spot in markup:
```ts
import quad from "#app/shared/assets/products/quad.jpg";
import sidecar from "#app/shared/assets/products/sidecar.jpg";
const CASES = [
{ name: "Quad", photo: quad },
{ name: "Sidecar", photo: sidecar },
];
```
```html
{c.name}
```
The URL is inlined at build time, so there is no runtime lookup and no helper
to call. An imported asset and a written path produce the identical URL. They
are one mechanism with two entry points.
Importing an asset makes it servable. That holds from server-only code too, so
a route that builds a webmanifest or an rss feed can import an icon and get a
real URL, and it holds even when nothing routes to the importing file: the
import is the reference, not the reachability of the code around it.
**Which to use:** a path when the file sits next to the template that uses it,
an import for anything shared or anything that has to travel as a value. The
scaffold imports its favicon because `app/shared/assets/` is nowhere near
`app/pages/home/`.
### `#` is for imports, not for URLs
`#app/...` is import-map syntax. It belongs in an `import` statement:
```ts
import favicon from "#app/shared/assets/favicons/favicon.svg";
```
In an attribute or a CSS `url()`, `#` means what it means in those languages.
A page anchor, an SVG fragment. It is left alone:
```html
Pricing
```
```css
.icon { fill: url(#gradient); } /* an SVG paint server */
```
### Bare imports
A clause-less import of an asset binds nothing, so there is nothing to use and
the statement is dropped:
```ts
import "#app/shared/assets/logo.svg"; // no effect
```
Only `.css` does something useful bare. It links the stylesheet:
```ts
import "./style.css";
```
## Dynamic assets
When the filename lives in a database row, no build-time URL exists. Store the
bytes and serve them from a route.
Store the file, with its content type and a hash you can cache on:
```sql
create table photos (
id uuid primary key default uuidGenerateV7(),
bytes bytea not null,
mimeType text not null,
hash text not null
);
```
Serve them from a route. A route that returns bytes is not a page, so it lives
in `app/routes/`:
```ts
// app/routes/photos.ts
import { Request, Response, sql } from "@elements/app";
interface Photo {
bytes: Buffer;
mimeType: string;
}
export default function servePhoto(req: Request, res: Response) {
let id = req.params.id;
let photo = sql(`
select bytes, mimeType from photos where id = ${id}::uuid
`).first();
if (!photo) {
res.status(404);
return "";
}
res.setHeader("Content-Type", photo.mimeType);
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
return photo.bytes;
}
```
```ts
// index.ts
app.route("/photos/:id", servePhoto);
```
Then the URL is ordinary data:
```html
```
The `?v=` is what makes `immutable` safe: the URL changes when the bytes
change, so a browser can hold the old one forever without ever showing stale
content. That is the same trick the static pipeline plays with a hashed
filename.
For uploads, accepting the file in the first place, see
`elements man recipes/file-upload`.