阿里云主机折上折
  • 微信号
Current Site:Index > Static file serving and resource hosting

Static file serving and resource hosting

Author:Chuan Chen 阅读数:26616人阅读 分类: Node.js

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

  1. Enable Compression:
const compression = require('compression');
app.use(compression());
app.use(express.static('public'));
  1. Use ETag:
app.use(express.static('public', {
  etag: true, // Enabled by default
  lastModified: true // Enabled by default
}));
  1. 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

  1. 404 Issue Troubleshooting:
// Debugging middleware
app.use('/static', (req, res, next) => {
  console.log('Request for:', req.path);
  next();
}, express.static('public'));
  1. MIME Type Errors:
app.use(express.static('public', {
  setHeaders: (res, path) => {
    if (path.endsWith('.wasm')) {
      res.set('Content-Type', 'application/wasm');
    }
  }
}));
  1. 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

  1. Access Control:
const auth = require('./auth-middleware');

app.use('/protected-assets', auth.checkLogin, express.static('private-files'));
  1. Access Logging:
const morgan = require('morgan');

app.use(morgan('combined'));
app.use(express.static('public'));
  1. 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

  1. Custom Index Files:
app.use(express.static('public', {
  index: ['index.html', 'default.html', 'home.html']
}));
  1. 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'));
  1. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.