Static file serving and resource hosting
Static File Serving and Resource Hosting
The Express framework comes with the built-in express.static
middleware, specifically designed for hosting static files. With simple configuration, it enables the server to serve static resources, which is particularly important for front-end development. Static resources typically refer to files that do not require server-side dynamic processing, such as images, CSS, JavaScript, font files, etc.
Basic Configuration and Usage
The simplest static file service requires just one line of code:
const express = require('express');
const app = express();
// Host static files from the 'public' directory
app.use(express.static('public'));
This line of code allows Express to automatically handle all requests for static files in the public
directory. For example:
public/images/logo.png
can be accessed via/images/logo.png
public/css/style.css
can be accessed via/css/style.css
Virtual Path Prefix
Sometimes, it's necessary to add a path prefix for static files:
app.use('/static', express.static('public'));
With this configuration:
public/images/logo.png
must be accessed via/static/images/logo.png
public/js/app.js
must be accessed via/static/js/app.js
This setup is particularly useful when distinguishing between API routes and static resources.
Hosting Multiple Directories
Express supports hosting multiple static directories simultaneously:
app.use(express.static('public'));
app.use(express.static('uploads'));
app.use('/assets', express.static('dist'));
With this configuration, Express will search for files in the order the middleware is registered. If both public
and uploads
contain image.jpg
, the file from the first registered directory will be returned.
Cache Control
Static files often require cache headers for better performance:
app.use(express.static('public', {
maxAge: '1d',
setHeaders: (res, path) => {
if (path.endsWith('.html')) {
res.setHeader('Cache-Control', 'no-cache');
}
}
}));
This configuration:
- Sets a default cache duration of 1 day
- Disables caching for HTML files
- Other files follow the default caching policy
Security Considerations
Static file serving requires attention to security:
app.use(express.static('public', {
dotfiles: 'ignore', // Ignore files starting with a dot
index: false, // Disable directory indexing
redirect: false // Disable automatic path correction
}));
A safer approach is to place the static directory outside the project root:
app.use('/static', express.static(path.join(__dirname, '..', 'static-assets')));
Advanced Configuration Example
A complete example combining multiple options:
const express = require('express');
const path = require('path');
const app = express();
app.use('/assets', express.static(path.join(__dirname, 'public'), {
maxAge: '30d',
immutable: true,
setHeaders: (res, filePath) => {
if (filePath.endsWith('.br')) {
res.set('Content-Encoding', 'br');
} else if (filePath.endsWith('.gz')) {
res.set('Content-Encoding', 'gzip');
}
},
fallthrough: false // Do not proceed to subsequent middleware if file is not found
}));
// Separate configuration for favicon
app.use('/favicon.ico', express.static(path.join(__dirname, 'public', 'images', 'favicon.ico')));
Performance Optimization Tips
- Enable Compression:
const compression = require('compression');
app.use(compression());
app.use(express.static('public'));
- Use ETag:
app.use(express.static('public', {
etag: true, // Enabled by default
lastModified: true // Enabled by default
}));
- Pre-compressed Files:
# Generate gzip and brotli compressed versions
gzip -k style.css
brotli -k style.css
Then automatically serve the compressed versions via middleware:
app.get('*.css', (req, res, next) => {
const acceptEncoding = req.headers['accept-encoding'];
if (acceptEncoding.includes('br') && fs.existsSync(req.path + '.br')) {
req.url = req.url + '.br';
res.set('Content-Encoding', 'br');
res.set('Content-Type', 'text/css');
} else if (acceptEncoding.includes('gzip') && fs.existsSync(req.path + '.gz')) {
req.url = req.url + '.gz';
res.set('Content-Encoding', 'gzip');
res.set('Content-Type', 'text/css');
}
next();
});
app.use(express.static('public'));
Practical Application Scenarios
Single Page Application (SPA) Deployment:
// Host static files
app.use(express.static(path.join(__dirname, 'dist')));
// Handle front-end routing
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});
Multi-environment Configuration:
const staticOptions = {
maxAge: process.env.NODE_ENV === 'production' ? '365d' : '0',
etag: process.env.NODE_ENV !== 'development'
};
app.use(express.static('public', staticOptions));
CDN Fallback Configuration:
app.use('/static', (req, res, next) => {
if (req.hostname === 'cdn.example.com') {
express.static('public')(req, res, next);
} else {
res.redirect(301, `https://cdn.example.com${req.url}`);
}
});
Common Issue Resolution
- 404 Issue Troubleshooting:
// Debugging middleware
app.use('/static', (req, res, next) => {
console.log('Request for:', req.path);
next();
}, express.static('public'));
- MIME Type Errors:
app.use(express.static('public', {
setHeaders: (res, path) => {
if (path.endsWith('.wasm')) {
res.set('Content-Type', 'application/wasm');
}
}
}));
- Path Encoding Issues:
// Handle filenames with Chinese characters
app.use(express.static('public', {
decode: (encoded) => {
try {
return decodeURIComponent(encoded);
} catch {
return encoded;
}
}
}));
Integration with Other Middleware
- Access Control:
const auth = require('./auth-middleware');
app.use('/protected-assets', auth.checkLogin, express.static('private-files'));
- Access Logging:
const morgan = require('morgan');
app.use(morgan('combined'));
app.use(express.static('public'));
- Hotlink Protection:
app.use('/images', (req, res, next) => {
const referer = req.get('referer');
if (!referer || !referer.includes('yourdomain.com')) {
return res.status(403).send('Access denied');
}
next();
}, express.static('public/images'));
File Upload and Download
Although static file serving is primarily for reading, it can be extended:
const multer = require('multer');
const upload = multer({ dest: 'public/uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
res.send(`File uploaded: <a href="/uploads/${req.file.filename}">View</a>`);
});
app.use(express.static('public'));
Version Control Strategy
Implementing versioned static resources:
// Generate filenames with hashes
app.use('/static', express.static('public'));
// Reference in HTML
app.get('/', (req, res) => {
const manifest = require('./public/manifest.json');
res.send(`
<link href="/static/${manifest['style.css']}" rel="stylesheet">
<script src="/static/${manifest['app.js']}"></script>
`);
});
Fine-tuning Static File Serving
- Custom Index Files:
app.use(express.static('public', {
index: ['index.html', 'default.html', 'home.html']
}));
- Automatic Extension Completion:
app.use((req, res, next) => {
if (!path.extname(req.path)) {
const possibleExtensions = ['.html', '.htm', '.xhtml'];
for (const ext of possibleExtensions) {
const filePath = path.join(__dirname, 'public', req.path + ext);
if (fs.existsSync(filePath)) {
req.url = req.url + ext;
break;
}
}
}
next();
});
app.use(express.static('public'));
- Custom Response Headers:
app.use(express.static('public', {
setHeaders: (res, path) => {
res.set('X-Content-Type-Options', 'nosniff');
res.set('X-Frame-Options', 'DENY');
res.set('Content-Security-Policy', "default-src 'self'");
}
}));
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:模板引擎集成与视图渲染
下一篇:错误处理机制与调试