Static Files
Use the statics() plugin to serve static assets like images, CSS, JavaScript, and other files with caching and compression support.
Basic setup
import { application, http, statics } from '@putnami/application';
export const app = () =>
application()
.use(http({ port: 3000 }))
.use(statics());Configuration
Plugin options
statics({
publicFolder: 'public', // Folder containing static files (default: 'public')
prefix: '/', // URL prefix for static files (optional)
cacheMaxAge: 86400, // Cache duration in seconds (default: 86400 / 1 day)
compress: 1024, // Min file size in bytes to gzip (default: 1024)
})URL prefix
Serve static files under a specific path:
statics({
prefix: '/assets',
publicFolder: 'public',
})Files in public/ will be available at /assets/:
public/logo.png→/assets/logo.pngpublic/css/style.css→/assets/css/style.css
Folder structure
my-app/
public/
favicon.ico
robots.txt
images/
logo.png
hero.jpg
css/
style.css
js/
app.js
src/
main.tsCaching
Cache-Control and ETag headers
Static files are served with both Cache-Control and ETag headers:
Cache-Control: public, max-age=86400
ETag: "a1b2c3d4e5f60718"The cacheMaxAge option controls the max-age value in seconds:
statics({
cacheMaxAge: 604800, // 1 week
})Conditional requests (304 Not Modified)
The plugin handles conditional requests automatically. When a browser has a cached copy of a file, it sends the stored ETag back in an If-None-Match header. If the ETag matches, the server responds with 304 Not Modified and no body, saving bandwidth.
Cache strategies
For development:
statics({
cacheMaxAge: 0, // No caching
})For production with versioned assets:
statics({
cacheMaxAge: 31536000, // 1 year
})For frequently updated files:
statics({
cacheMaxAge: 3600, // 1 hour
})Compression
Files larger than the compress threshold are gzipped at build time and served with the Content-Encoding: gzip header:
statics({
compress: 1024, // Gzip files > 1KB (default)
})Set to 0 to disable compression:
statics({
compress: 0,
})Content types
The plugin automatically detects content types based on file extensions:
- .html -
text/html - .css -
text/css - .js -
application/javascript - .json -
application/json - .png -
image/png - .jpg, .jpeg -
image/jpeg - .gif -
image/gif - .svg -
image/svg+xml - .webp -
image/webp - .woff -
font/woff - .woff2 -
font/woff2 - .pdf -
application/pdf
Multiple static folders
Serve files from multiple locations:
import { application, http, statics } from '@putnami/application';
export const app = () =>
application()
.use(http({ port: 3000 }))
.use(statics({ publicFolder: 'public' }))
.use(statics({ publicFolder: 'uploads', prefix: '/uploads' }));Build-time assets
Configuring assets in package.json
{
"putnami": {
"application": {
"assets": [
{
"from": "/apps/my-app/doc",
"to": "public/docs"
},
{
"from": "/LICENSE.md",
"to": "public/LICENSE.md"
}
]
}
}
}Generated assets
Assets generated during build (like compiled CSS or bundled JS) are typically placed in .gen/public/:
.gen/
public/
css/
main.css
js/
bundle.jsReact integration
When using @putnami/react, static files work alongside SSR:
import { application, http, statics } from '@putnami/application';
import { react } from '@putnami/react';
export const app = () =>
application()
.use(http({ port: 3000 }))
.use(react())
.use(statics({ publicFolder: 'public' }));Referencing static files in React
export default function Page() {
return (
<div>
<img src="/images/logo.png" alt="Logo" />
<link rel="stylesheet" href="/css/style.css" />
</div>
);
}Public folder in React config
react({
publicFolder: 'public', // Static files folder
})Security considerations
Prevent directory traversal
The statics plugin automatically prevents directory traversal attacks. Requests like /../../etc/passwd are blocked.
Hide sensitive files
Don't place sensitive files in the public folder:
public/ # Served to users
images/
css/
private/ # Not served
secrets.json
.envExclude files
Use .gitignore patterns in your public folder if needed:
public/
.gitignore # Contains patterns to ignore
images/Performance tips
Use CDN for production
In production, consider serving static files from a CDN:
const isProduction = process.env.NODE_ENV === 'production';
// In React components
const assetPrefix = isProduction ? 'https://cdn.example.com' : '';
<img src={`${assetPrefix}/images/logo.png`} />Compress files
Enable build-time compression for large assets:
statics({
compress: 1024, // Gzip files > 1KB
})Image optimization
Optimize images before deployment:
# Using imagemagick
mogrify -strip -quality 85 public/images/*.jpg
# Using optipng
optipng -o7 public/images/*.pngCommon patterns
Favicon
public/
favicon.ico
apple-touch-icon.png
favicon-32x32.png
favicon-16x16.png// In layout.tsx
<head>
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
</head>Robots.txt
# public/robots.txt
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xmlManifest for PWA
// public/manifest.json
{
"name": "My App",
"short_name": "App",
"start_url": "/",
"display": "standalone",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
}
]
}