Separating Frontend and Backend Static Assets in Express.js Application

I’m building a web app using Express.js and need help with organizing my project structure. My goal is to create two separate sections - one for displaying content from a database (public-facing) and another for managing that content (admin panel).

My Project Layout

project/
  ├── public-site/
  │     ├── assets //CSS, JS & images for public site
  │     ├── templates //Public site jade views
  │     └── routes.js
  │
  ├── admin-panel/
  │     ├── assets //Admin-only CSS & resources
  │     └── templates //Admin templates
  │     └── admin-routes.js
  │
  └── main.js //Application entry point

Main Application File (main.js)

var express = require('express');
var application = express();

var settings = require('../project/settings.json')[application.get('env')];

var adminModule = require('./admin-panel/admin-routes');
var publicModule = require('./public-site/routes');

application.use(adminModule);
application.use(publicModule);

application.set('port', settings.port || 3000);

var httpServer = application.listen(application.get('port'), function() {
    console.log('Application running on port ' + application.get('port') + ' in ' + application.get('env') + ' environment');
});

Public Site Routes (routes.js)

var express = require('express');
var router = express();

router.set('views', __dirname + '/templates');
router.set('view engine', 'jade');

router.use(express.static(__dirname + '/assets'));

router.get('/', function(request, response) {
    response.render('page', {pageTitle: 'Homepage'});
});

router.get('/contact', function(request, response) {
    response.render('page', {pageTitle: 'Contact Page'});
});

module.exports = router;

Admin Panel Routes (admin-routes.js)

var express = require('express');
var adminRouter = express();

adminRouter.set('views', __dirname + '/templates');
adminRouter.set('view engine', 'jade');

adminRouter.use(express.static(__dirname + '/assets'));

adminRouter.get('/admin', function(request, response) {
    response.render('page', {pageTitle: 'Admin Login'});
});

adminRouter.get('/admin/panel', function(request, response) {
    response.render('page', {pageTitle: 'Control Panel'});
});

module.exports = adminRouter;

The Problem

The issue I’m facing is that Express.js serves the wrong stylesheets for different routes. All pages are loading the admin panel CSS instead of their respective stylesheets.

When I visit localhost:3000/contact, it should use CSS from public-site/assets/css/ but instead it loads styles from admin-panel/assets/css/.

How can I configure Express to serve the correct static files for each section of my application?

hmm interesting setup! are you loading the css files with absolute paths in your jade templates? like /css/style.css instead of relative paths? that might explain why its always picking up the last registered static middleware. what does your template structure look like for referencing assets?

The root cause of your CSS conflict lies in how Express handles static middleware registration. Both modules are registering their static directories at the root level, causing the last registered middleware to take precedence for overlapping file paths. I encountered this exact scenario when building a multi-tenant application. The solution is to namespace your static assets using URL prefixes. Modify your route files to specify unique paths for each section’s assets. In your public routes, change the static middleware to router.use('/public', express.static(__dirname + '/assets')) and update your templates to reference assets via /public/css/style.css. Similarly, in admin routes, use adminRouter.use('/admin-assets', express.static(__dirname + '/assets')) and reference them as /admin-assets/css/admin.css in your admin templates. This approach ensures Express serves assets from the correct directory based on the URL path, eliminating the conflict while maintaining clean separation between your public and admin interfaces.

looks like your static middleware is conflicting becuase both are mounted at root level. try using app.use('/admin', adminModule) and app.use('/', publicModule) in main.js instead of mounting both without paths. this way admin assets will be served under /admin prefix and wont clash with public assets.