DEV Community

Olusegun Olugbenga Adeleke
Olusegun Olugbenga Adeleke

Posted on

Building a Traffic-Splitting URL Shortener with Node.js, React, and Render

Deploying a full stack JavaScript application sounds straightforward until subtle runtime differences surface in production. What works locally can fail in a managed environment. This article walks through the architecture and deployment of a traffic-splitting URL shortener built with Node.js, Express, React, and Render, including a key issue encountered with Express version compatibility.


Architecture Overview

The system implements the following:

  • URL shortening with unique short codes
  • Traffic splitting between two destination URLs
  • Hit tracking per destination
  • Computed total hit aggregation
  • Password-protected admin dashboard
  • React frontend served from the same backend service
  • Deployment as a single Web Service on Render

The backend uses:

  • nanoid for generating short codes
  • bcrypt for secure password validation

An in-memory Map is used as a lightweight data store:

const cache = new Map();
Enter fullscreen mode Exit fullscreen mode

Each short code maps to an object containing metadata, two destination URLs, hit counters, and a computed getter.


Traffic Splitting Logic

The redirect route accepts a short code and randomly selects one of two stored URLs:

const idx = Math.random() < 0.5 ? 0 : 1;
data.urls[idx].hits += 1;
res.redirect(data.urls[idx].link);
Enter fullscreen mode Exit fullscreen mode

This approach enables:

  • Basic A/B testing
  • Lightweight traffic experimentation
  • Simple load distribution scenarios

Each redirect increments the hit counter for the selected destination.


Computed Properties with Getters

Instead of storing a derived value, total hits are computed dynamically:

get totalHits() {
  return this.urls.reduce((sum, url) => sum + url.hits, 0);
}
Enter fullscreen mode Exit fullscreen mode

This avoids duplication and guarantees consistency between individual hit counters and aggregate metrics.


Protecting a GET Route

Although GET endpoints are often associated with public access, they can be protected using middleware. The admin dashboard requires a password passed as a query parameter and validated using bcrypt:

const protectAdmin = async (req, res, next) => {
  const password = req.query.password;
  if (!password) return res.status(401).send("Input password");

  const match = await bcrypt.compare(password, hashedPassword);
  if (!match) return res.status(401).send("Invalid password");

  next();
};
Enter fullscreen mode Exit fullscreen mode

Applied as:

app.get('/admin/dashboard', protectAdmin, (req, res) => {
  res.send(cacheData);
});
Enter fullscreen mode Exit fullscreen mode

This ensures the route remains inaccessible without proper authentication.


Integrating the React Frontend

The frontend is created using a standard React scaffold and built into static assets:

npm run build
Enter fullscreen mode Exit fullscreen mode

The production build is served directly from Express:

app.use(express.static(path.join(__dirname, "frontend/build")));
Enter fullscreen mode Exit fullscreen mode

Serving the frontend from the same origin as the API eliminates CORS complexity and simplifies deployment.


Deployment on Render

The application is deployed as a single Web Service on Render. The backend serves both API routes and static frontend assets.

Key deployment requirements:

  • Install dependencies
  • Build the React frontend during deployment
  • Use the dynamic port provided by Render
const port = process.env.PORT || 3000;
app.listen(port);
Enter fullscreen mode Exit fullscreen mode

Express Wildcard Route Issue on Render

During deployment, a routing error occurred related to wildcard route definitions such as:

app.get('*', ...)
Enter fullscreen mode Exit fullscreen mode

and

app.get('/:path(*)', ...)
Enter fullscreen mode Exit fullscreen mode

In the Render environment, newer versions of Express rely on a stricter version of path-to-regexp, which rejects certain wildcard patterns. This caused the application to crash with a PathError.

The resolution was to downgrade Express to a stable version below v5, specifically within the Express v4 range. Express v4 uses a more permissive routing parser and correctly handles wildcard routes such as:

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, "frontend/build", "index.html"));
});
Enter fullscreen mode Exit fullscreen mode

Pinning Express to a stable v4 version in package.json resolved the deployment issue:

"express": "^4.18.2"
Enter fullscreen mode Exit fullscreen mode

After redeployment, routing behaved as expected and the application started successfully.


Key Takeaways

  1. Version differences between local and production environments can affect routing behavior.
  2. Express v5 introduces stricter route parsing via path-to-regexp.
  3. Pinning dependencies explicitly prevents unexpected runtime incompatibilities.
  4. Serving React from the same Express backend simplifies CORS and deployment.
  5. Middleware provides clean protection for sensitive GET endpoints.

Conclusion

A traffic-splitting URL shortener may appear simple at first glance, but it involves considerations around routing, state management, authentication, frontend integration, and deployment environment consistency.

The most important lesson is not the redirect logic itself, but the operational awareness required to ship reliably. Dependency versions, route definitions, and hosting environments all influence behavior in production. Controlling these variables is essential for building stable full stack systems.

Top comments (0)