No Framework Overkill — Just Express and JSX
Leapcell: The Best of Serverless Web Hosting Server-Side Rendering in Express.js: In-Depth Comparison of EJS and JSX (TypeScript Practice) Node.js combined with Express.js remains a golden combination for building efficient web applications. When we need to provide dynamic HTML content to the client, Express introduces the concept of a "view engine". Over the years, EJS (Embedded JavaScript) has become a popular choice due to its simplicity. However, since the advent of React, JSX (JavaScript XML), with its component-based UI construction approach, has gained enormous favor among developers, and its philosophy is also fully applicable to server-side rendering. This article will delve into how to use traditional EJS and modern JSX to implement server-side rendering (SSR) in an Express.js application developed with TypeScript. We will compare their advantages and disadvantages, specific implementation methods, and discuss how to conveniently deploy the built application to a cloud platform. 1. Introduction: Server-Side Rendering and View Engines Server-Side Rendering (SSR) is a technology where a complete HTML page is generated on the server side and then sent to the client. This method can effectively improve the first-screen loading speed and is friendly to search engine optimization (SEO). Express.js simplifies the process of generating dynamic HTML through its view engine mechanism. The core responsibility of a view engine is to combine template files with dynamic data and compile them into the final HTML string. Express itself does not bundle a specific view engine; developers can freely select and configure it via app.set('view engine', 'engine_name'). 2. EJS: A Classic Template Engine 2.1 Overview and Core Features of EJS As the name suggests, EJS (Embedded JavaScript) allows developers to embed JavaScript code in HTML templates. For developers familiar with traditional server-side scripting languages like PHP and ASP, EJS syntax is very intuitive and easy to understand. Main EJS tags: : Escapes and outputs the result of a JavaScript expression into HTML (prevents XSS attacks). : Outputs the result of a JavaScript expression into HTML without escaping (for scenarios where HTML content is intentionally embedded). : Used to execute JavaScript control flow statements (such as if conditionals, for loops, etc.). : A comment tag whose content is neither executed nor output. : Imports and renders another EJS file. 2.2 Using EJS in Express (TypeScript) First, install the relevant dependencies: npm install express ejs npm install --save-dev @types/express @types/ejs typescript nodemon ts-node A basic tsconfig.json configuration example: { "compilerOptions": { "target": "ES2022", // Target JavaScript version "module": "commonjs", // Common module system for Node.js environment "rootDir": "./src", // TypeScript source file directory "outDir": "./dist", // Compiled JavaScript file output directory "esModuleInterop": true, // Enables interoperability between CommonJS and ES Modules "strict": true, // Enables all strict type-checking options "skipLibCheck": true // Skips type checking of declaration files }, "include": ["src/**/*"], // Specifies files to be compiled "exclude": ["node_modules"] // Specifies files to be excluded from compilation } Example code for src/server.ts: import express, { Request, Response } from 'express'; import path from 'path'; const app = express(); const port = process.env.PORT || 3001; // Port number can be customized // Set EJS as the view engine app.set('view engine', 'ejs'); // Set the template file directory, e.g., 'src/views' app.set('views', path.join(__dirname, 'views')); app.get('/', (req: Request, res: Response) => { res.render('index', { // Renders views/index.ejs title: 'EJS Demo Page', message: 'Welcome to the EJS template driven by Express and TypeScript!', user: { name: 'Guest', isAdmin: false }, items: ['Apple', 'Banana', 'Cherry'] }); }); app.listen(port, () => { console.log(`EJS example server running at http://localhost:${port}`); }); Example template for src/views/index.ejs: body { font-family: Arial, sans-serif; padding: 20px; } .user-greeting { color: blue; } .admin-panel { border: 1px solid red; padding: 10px; margin-top: 10px; } Hello, ! Welcome, administrator! This is the admin panel. You currently have regular user privileges. Product List: 0) { %> No products available. src/views/partials/footer.ejs (for include): © My Website. All rights reserved. 2.3 Advantages of EJS Simple and Intuitive: Gentle learning curve, very friendly to developers with HTML and basic JavaScript knowledge. Highly Flexible: Allows direct embedding

Leapcell: The Best of Serverless Web Hosting
Server-Side Rendering in Express.js: In-Depth Comparison of EJS and JSX (TypeScript Practice)
Node.js combined with Express.js remains a golden combination for building efficient web applications. When we need to provide dynamic HTML content to the client, Express introduces the concept of a "view engine". Over the years, EJS (Embedded JavaScript) has become a popular choice due to its simplicity. However, since the advent of React, JSX (JavaScript XML), with its component-based UI construction approach, has gained enormous favor among developers, and its philosophy is also fully applicable to server-side rendering.
This article will delve into how to use traditional EJS and modern JSX to implement server-side rendering (SSR) in an Express.js application developed with TypeScript. We will compare their advantages and disadvantages, specific implementation methods, and discuss how to conveniently deploy the built application to a cloud platform.
1. Introduction: Server-Side Rendering and View Engines
Server-Side Rendering (SSR) is a technology where a complete HTML page is generated on the server side and then sent to the client. This method can effectively improve the first-screen loading speed and is friendly to search engine optimization (SEO). Express.js simplifies the process of generating dynamic HTML through its view engine mechanism.
The core responsibility of a view engine is to combine template files with dynamic data and compile them into the final HTML string. Express itself does not bundle a specific view engine; developers can freely select and configure it via app.set('view engine', 'engine_name')
.
2. EJS: A Classic Template Engine
2.1 Overview and Core Features of EJS
As the name suggests, EJS (Embedded JavaScript) allows developers to embed JavaScript code in HTML templates. For developers familiar with traditional server-side scripting languages like PHP and ASP, EJS syntax is very intuitive and easy to understand.
Main EJS tags:
-
<%= ... %>
: Escapes and outputs the result of a JavaScript expression into HTML (prevents XSS attacks). -
<%- ... %>
: Outputs the result of a JavaScript expression into HTML without escaping (for scenarios where HTML content is intentionally embedded). -
<% ... %>
: Used to execute JavaScript control flow statements (such asif
conditionals,for
loops, etc.). -
<%# ... %>
: A comment tag whose content is neither executed nor output. -
<%- include('path/to/template') %>
: Imports and renders another EJS file.
2.2 Using EJS in Express (TypeScript)
First, install the relevant dependencies:
npm install express ejs
npm install --save-dev @types/express @types/ejs typescript nodemon ts-node
A basic tsconfig.json
configuration example:
{
"compilerOptions": {
"target": "ES2022", // Target JavaScript version
"module": "commonjs", // Common module system for Node.js environment
"rootDir": "./src", // TypeScript source file directory
"outDir": "./dist", // Compiled JavaScript file output directory
"esModuleInterop": true, // Enables interoperability between CommonJS and ES Modules
"strict": true, // Enables all strict type-checking options
"skipLibCheck": true // Skips type checking of declaration files
},
"include": ["src/**/*"], // Specifies files to be compiled
"exclude": ["node_modules"] // Specifies files to be excluded from compilation
}
Example code for src/server.ts
:
import express, { Request, Response } from 'express';
import path from 'path';
const app = express();
const port = process.env.PORT || 3001; // Port number can be customized
// Set EJS as the view engine
app.set('view engine', 'ejs');
// Set the template file directory, e.g., 'src/views'
app.set('views', path.join(__dirname, 'views'));
app.get('/', (req: Request, res: Response) => {
res.render('index', { // Renders views/index.ejs
title: 'EJS Demo Page',
message: 'Welcome to the EJS template driven by Express and TypeScript!',
user: { name: 'Guest', isAdmin: false },
items: ['Apple', 'Banana', 'Cherry']
});
});
app.listen(port, () => {
console.log(`EJS example server running at http://localhost:${port}`);
});
Example template for src/views/index.ejs
:
lang="zh-CN">
charset="UTF-8">
name="viewport" content="width=device-width, initial-scale=1.0">
< %= title %>
body { font-family: Arial, sans-serif; padding: 20px; }
.user-greeting { color: blue; }
.admin-panel { border: 1px solid red; padding: 10px; margin-top: 10px; }
<
%= message %>
<% if (user && user.name) { %>
class="user-greeting">Hello, <%= user.name %>!
<% if (user.isAdmin) { %>
class="admin-panel">Welcome, administrator! This is the admin panel.
<% } else { %>
You currently have regular user privileges.
<% } %>
<% } %>
Product List:
<% if (items && items.length > 0) { %>
<% items.forEach(function(item) { %>
< %= item %>
<% }); %>
<% } else { %>
No products available.
<% } %>
<%- include('partials/footer', { year: new Date().getFullYear() }) %>
src/views/partials/footer.ejs
(for include
):
© <%= year %> My Website. All rights reserved.
2.3 Advantages of EJS
- Simple and Intuitive: Gentle learning curve, very friendly to developers with HTML and basic JavaScript knowledge.
- Highly Flexible: Allows direct embedding and execution of arbitrary JavaScript code in templates, offering relative freedom in handling complex logic.
- Widely Used and Mature Ecosystem: As an established template engine, it has a large number of existing projects and community support.
2.4 Limitations of EJS
-
Lack of Type Safety: Even if the main project uses TypeScript, data passed to EJS templates is almost of type
any
within the template. This makes it difficult to detect issues like property name spelling errors or data structure mismatches during compilation, prone to runtime errors. - Readability and Maintainability Challenges: When templates embed excessive or complex JavaScript logic, HTML structure and business logic become highly coupled, making the code difficult to read and maintain.
-
Weak Componentization Capability: Although the
include
directive can achieve template fragment reuse, compared to the declarative and composable component model provided by JSX, EJS struggles to build large and complex UIs. -
Limited IDE Support: In
.ejs
files, powerful features of TypeScript like type checking, intelligent prompts, and refactoring cannot be fully utilized.
3. JSX: A Syntax Extension for UI Construction
3.1 Overview and Core Features of JSX (Not Just for React)
JSX (JavaScript XML) is a syntax extension for JavaScript that allows developers to write HTML-like (or XML) structures in JavaScript code. Although initially designed for React, JSX itself is an independent specification that can be compiled into any target code, not exclusive to React. On the server side, we can leverage JSX's declarative features to describe UI structures and then convert them into HTML strings.
JSX code cannot be directly executed by browsers or the Node.js environment; it needs to be transpiled into standard JavaScript function calls (such as React.createElement()
or custom equivalent functions) via tools like Babel, the TypeScript compiler (tsc
), or esbuild
.
3.2 Why Choose JSX for Server-Side Rendering?
For developers accustomed to modern front-end frameworks like React and Vue, JSX (or similar template syntax) is a natural choice for building componentized UIs. Introducing it into server-side rendering offers numerous benefits:
- Consistency Between Frontend and Backend: Enables similar componentized design thinking and development patterns on both the server and client sides.
- Type Safety: Combined with TypeScript, it allows defining clear types for component Props (properties), enjoying the robustness brought by compile-time type checking.
- Declarative and Structured: UI code is more declarative, with clearer structure, making it easier to understand and maintain.
- Component Reuse: Facilitates easy creation and reuse of UI components, improving development efficiency.
3.3 Configuring a JSX Environment in Express (TypeScript)
To use JSX for server-side rendering in Express, some configuration is required. We will use react
and react-dom/server
to convert JSX components into HTML strings. Note that this is different from client-side React; here, we only utilize its JSX parsing and string generation capabilities, without involving virtual DOM operations or client-side lifecycles.
3.3.1 Installing Necessary Dependencies
npm install express react react-dom
npm install --save-dev @types/express @types/react @types/react-dom typescript nodemon ts-node esbuild esbuild-register
esbuild
is a blazing-fast JavaScript/TypeScript bundling and transpiling tool. esbuild-register
can instantly transpile .ts
and .tsx
files during development, making it very convenient. In production, pre-building is usually recommended.
3.3.2 Configuring tsconfig.json
To ensure the TypeScript compiler correctly processes JSX syntax, the following configuration is required in tsconfig.json
:
{
"compilerOptions": {
// ... Other configurations remain unchanged ...
"jsx": "react-jsx", // Recommended for new JSX transformation, no need to manually import React
// "jsx": "react", // Legacy JSX transformation, requires importing React in each .tsx file: import React from 'react';
"esModuleInterop": true,
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
The "jsx": "react-jsx"
option enables the new JSX transformation, which automatically imports necessary helper functions. Typically, there is no need to write import React from 'react';
at the top of each JSX file (unless you explicitly use other React APIs like Hooks, which are usually unnecessary in pure SSR components).
3.3.3 Implementing a Custom JSX View Engine
Express requires a custom view engine to handle .tsx
files. This engine is responsible for require
ing (or dynamically import
ing) the compiled .tsx
files (i.e., .js
files), obtaining the exported components, and using ReactDOMServer.renderToString()
to convert them and their props into HTML strings.
Configure in src/server.tsx
(or src/app.ts
):
// src/server.tsx or src/app.ts
import express, { Request, Response, NextFunction } from 'express';
import path from 'path';
import ReactDOMServer from 'react-dom/server'; // For server-side rendering
import fs from 'fs'; // Node.js file system module
// In development mode, use esbuild-register for instant compilation of .ts/.tsx files
// In production, pre-compile src to the dist directory and run .js files from dist
if (process.env.NODE_ENV !== 'production') {
require('esbuild-register')({
extensions: ['.ts', '.tsx'], // Ensure .tsx files are processed
// target: 'node16' // Set according to your Node.js version
});
}
const app = express();
const port = process.env.PORT || 3002;
// Custom JSX (.tsx) view engine
app.engine('tsx', async (filePath: string, options: object, callback: (e: any, rendered?: string) => void) => {
try {
// Dynamically import the compiled module (handled by esbuild-register or tsc)
// Note: The require cache mechanism may affect hot updates; no such issue in production builds
// For more reliable hot updates during development, more complex setups like clearing require.cache may be needed
// delete require.cache[require.resolve(filePath)]; // Simple cache-clearing example, may have side effects
const { default: Component } = await import(filePath); // Assume the component is a default export
if (!Component) {
return callback(new Error(`Component not found or not default-exported from ${filePath}`));
}
// Use React's API to render the component into an HTML string
// The options object is passed to the component as props
const html = ReactDOMServer.renderToString(React.createElement(Component, options));
// Typically needs to wrap
callback(null, `${html}`);
} catch (e) {
callback(e);
}
});
app.set('views', path.join(__dirname, 'views')); // Set the view file directory, e.g., 'src/views'
app.set('view engine', 'tsx'); // Set .tsx as the default view engine extension
// ... Routes will be defined later ...
Notes:
-
await import(filePath)
is used for dynamic imports. In thecommonjs
module system, this typically works normally after outputting to thedist
directory.esbuild-register
also handles this well. - Hot Reloading During Development: Simple
require
orimport
will be cached by Node.js. For immediate生效 of component modifications during development, additional hot module replacement (HMR) mechanisms or manual clearing ofrequire.cache
may be required (as shown in the example comments, but not recommended for complex scenarios). Production builds do not have this issue. -
React.createElement(Component, options)
is a more standard usage thanComponent(options)
, although the latter may work in simple scenarios.
3.4 Creating JSX Components
Create JSX components (.tsx
files) in the src/views
directory.
3.4.1 Layout Component (Layout Component)
Create a universal page layout component to wrap all page content and provide a consistent HTML skeleton.
src/views/layouts/MainLayout.tsx
:
// With "jsx": "react-jsx" set in tsconfig.json, importing React from 'react' is usually unnecessary
// But if using specific React APIs (like createContext, useState, etc.), importing is still required
// import React from 'react';
interface MainLayoutProps {
title: string;
children: React.ReactNode; // To receive child components
lang?: string;
}
// React.FC (Functional Component) is an optional type that provides some conveniences
const MainLayout: React.FC<MainLayoutProps> = ({ title, children, lang = "zh-CN" }) => {
return (
<html lang={lang}>
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
{/* Global CSS links, meta tags, etc., can be added here */}
<link rel="stylesheet" href="/styles/global.css" /> {/* Assumes a global stylesheet exists */}
<style dangerouslySetInnerHTML={{ __html: `
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background-color: #f4f4f4; color: #333; }
header { background-color: #333; color: white; padding: 1rem; text-align: center; }
main { background-color: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
footer { text-align: center; margin-top: 2rem; color: #666; }
`}} />
</head>
<body>
<header>
<h1>{title}</h1>
</header>
<main>
{children} {/* Child components will be rendered here */}
</main>
<footer>
<p>© {new Date().getFullYear()} Leapcell Technical Demo</p>
</footer>
</body>
</html>
);
};
export default MainLayout;
Important: When using React.FC
, starting from @types/react
v18, children
is no longer an implicit property and must be explicitly declared in the Props
type as children: React.ReactNode;
.
3.4.2 Page Component (Page Component)
Create components for specific pages.
src/views/pages/HomePage.tsx
:
import MainLayout from '../layouts/MainLayout'; // Import the layout component
interface HomePageProps {
pageTitle: string;
welcomeMessage: string;
features: Array<{ id: number; name: string; description: string }>;
currentUser?: string; // Optional property
}
const HomePage: React.FC<HomePageProps> = (props) => {
const { pageTitle, welcomeMessage, features, currentUser } = props;
return (
<MainLayout title={pageTitle}>
<h2>{welcomeMessage}</h2>
{currentUser && <p>Current User: <strong>{currentUser}</strong>p>}
<h3>Core Features:</h3>
{features.length > 0 ? (
<ul>
{features.map(feature => (
<li key={feature.id}>
<strong>{feature.name}:</strong> {feature.description}
</li>
))}
</ul>
) : (
<p>No feature introductions available.</p>
)}
<div style={{ marginTop: '20px', padding: '10px', border: '1px dashed #ccc' }}>
<p>This is an example area with inline styles.</p>
</div>
</MainLayout>
);
};
export default HomePage; // Ensure default export
3.5 Rendering JSX in Express Routes
Return to src/server.tsx
(or src/app.ts
) and add routes to render the created JSX page components.
// src/server.tsx (or src/app.ts) (continued from above)
// Example: Static resource middleware for serving CSS, JS, images, etc.
// Assume you created a stylesheet at src/public/styles/global.css
app.use(express.static(path.join(__dirname, 'public')));
// Note: If __dirname points to dist/src, the public directory should be adjusted accordingly
// Typically, the public directory is placed at the project root or alongside src, and copied to dist during build
app.get('/', (req: Request, res: Response) => {
const homePageData = {
pageTitle: 'JSX SSR Home Page',
welcomeMessage: 'Welcome to experience Express + TypeScript + JSX server-side rendering!',
features: [
{ id: 1, name: 'Type Safety', description: 'Strong type support for Props via TypeScript.' },
{ id: 2, name: 'Componentization', description: 'Split UI into reusable components.' },
{ id: 3, name: 'Modern Development Experience', description: 'Enjoy a development pattern consistent with modern front-end frameworks.' }
],
currentUser: 'Explorer Leap'
};
res.render('pages/HomePage', homePageData); // Renders views/pages/HomePage.tsx
});
app.get('/info', (req: Request, res: Response) => {
// Assume you created src/views/pages/InfoPage.tsx
// res.render('pages/InfoPage', { /* ... props ... */ });
// Simple example: Directly return HTML string (not recommended, for demonstration only)
// Better practice: Create a .tsx component for InfoPage too
const InfoComponent = () => (
<MainLayout title="Info Page">
<p>This is a simple info page also rendered by JSX on the server side.</p>
<p>Current Time: {new Date().toLocaleTimeString()}</p>
</MainLayout>
);
const html = ReactDOMServer.renderToString(<InfoComponent />);
res.send(`${html}`);
});
app.listen(port, () => {
console.log(`JSX example server running at http://localhost:${port}`);
if (process.env.NODE_ENV !== 'production') {
console.log('Development mode: Using esbuild-register for instant TSX transpilation.');
} else {
console.log('Production mode: Ensure you are running pre-compiled JS files from the dist directory.');
}
});
Now, accessing the /
route, Express will use our defined tsx
engine to load the HomePage.tsx
component, pass homePageData
as props, and render it into an HTML string to return to the browser.
3.6 Advantages of JSX in SSR
- Strong Type Safety: The combination of TypeScript and JSX is a perfect match. You can define precise interfaces (interface) or types (type) for component Props, and the compiler will catch type mismatches, missing properties, or spelling errors during development, greatly enhancing code robustness and maintainability.
- Excellent Componentization Capability: UI can be clearly divided into highly cohesive and low-coupled components. These components are not only easy to understand and test but also can be reused across different pages or even projects. Concepts like layout components and atomic components can be easily implemented.
-
Improved Developer Experience (DX):
- IDE Support: Mainstream IDEs (e.g., VS Code) provide excellent support for TSX, including intelligent code completion, type hints, refactoring, error highlighting, etc.
- Declarative Programming: JSX's declarative syntax makes UI structure more intuitive, with code closer to the final visual representation.
- Ecosystem Integration: Can leverage some rendering-agnostic libraries or design patterns in the React ecosystem.
- Code Consistency: If the front end also uses React or similar JSX-based frameworks, the server and client can use similar component writing styles and logic, reducing team members' learning costs and context-switching overhead.
3.7 Potential Challenges of JSX in SSR
-
Build Configuration: JSX requires a compilation step (TypeScript compiler, Babel, or esbuild) to convert into executable JavaScript for browsers or Node.js. Although
esbuild-register
simplifies the development process, production deployment still requires a reasonable build strategy. -
Slight Performance Overhead: Compared to direct string concatenation or very lightweight template engines, JSX compilation and
ReactDOMServer.renderToString()
calls introduce some performance overhead. However, in most application scenarios, this overhead is usually negligible and can be optimized through caching strategies. - Mental Model: Developers unfamiliar with React or JSX need some learning time to adapt to its componentized thinking and functional programming paradigm.
-
Server-Side Limitations: In pure SSR scenarios, React's Hooks (e.g.,
useState
,useEffect
) are usually meaningless because server-side rendering is a one-time process, not involving client-side interactions or state updates. Components should mainly rely on props to receive data and render.
4. EJS vs. JSX: Comprehensive Comparison
Feature | EJS (Embedded JavaScript) | JSX (with TypeScript) |
---|---|---|
Syntax and Readability | HTML with embedded JS (<% ... %> ). Simple logic is clear; complex logic becomes messy. |
JS with embedded HTML-like structures. Componentized, complex UI structures are clearer. |
Type Safety | Weak. Data passed to templates has almost no type checking within templates, prone to runtime errors. | Strong. Props typed via TypeScript, compile-time checking, highly robust. |
Componentization and Reuse | Limited (include ). Difficult to achieve true componentization and state isolation. |
Core feature. Natively supports highly reusable and composable components. |
Development Experience (DX) | Limited IDE support, relatively difficult debugging, inconvenient refactoring. | Strong IDE support (intelligent hints, type checking, refactoring), friendly debugging. |
Learning Curve | Low. Familiarity with HTML and JS suffices. | Slightly higher. Requires understanding components, Props, JSX compilation, etc. |
Performance | Generally very lightweight, fast parsing. | Compilation and renderToString have some overhead, but acceptable in most scenarios. |
Ecosystem and Toolchain | Simple, low dependencies. | Relies on React-related libraries and compilation tools (tsc, esbuild, Babel). |
Logic Handling | Allows writing complex JS logic directly in templates (not recommended). | Recommends placing logic in component methods/hooks (if applicable) or passed props. |
5. Technology Selection Recommendations: When to Choose EJS or JSX?
Scenarios for Choosing EJS:
- Very Simple Projects: Few pages, uncomplex UI logic, pursuing rapid prototyping.
- Team Unfamiliar with React/JSX: Lack of sufficient time or willingness to learn.
- Strict Restrictions on Build Steps: Desire to minimize compilation phases.
- Legacy Project Maintenance: Existing large EJS template base, high migration cost.
Scenarios for Choosing JSX (with TypeScript):
- Pursuing High Robustness and Maintainability: Type safety is a primary consideration.
- Building Complex and Scalable UIs: Requiring strong componentization capabilities.
- Team Familiar with React/JSX or Adopting Modern Front-End Tech Stacks: Improving development efficiency and code quality.
- Unified Frontend-Backend Tech Stacks: Maintain technical consistency if the front end also uses React.
- Medium to Large Projects: Long-term benefits of componentization and type safety far exceed initial investment.
In summary, for new Node.js server-side rendering projects with some complexity, strongly recommend using JSX with TypeScript. The improvements in type safety, componentization, and development experience can significantly enhance project quality and long-term maintainability.
6. Deploying Applications to Cloud Platforms (e.g., Leapcell)
Whether built with EJS or modern JSX, deploying an Express application to a cloud platform is quite straightforward. Modern cloud hosting platforms like Leapcell provide convenient deployment and management experiences for Node.js applications.
The typical deployment process includes:
-
Code Preparation: Ensure your
package.json
defines a start script, e.g.:
"scripts": {
"start": "node dist/server.js", // Start compiled JS files in production
"build": "tsc" // Or use esbuild: "esbuild src/server.tsx --bundle --outfile=dist/server.js --platform=node --format=cjs"
}
-
Building: Execute the build command (e.g.,
npm run build
) to generate thedist
directory before deployment. -
Platform Configuration: On platforms like Leapcell:
- Connect to your code repository (e.g., GitHub).
- Configure the build command (if the platform supports auto-building, it usually reads the
build
script frompackage.json
). - Configure the start command (e.g.,
npm start
or directnode dist/server.js
). - Set environment variables (e.g.,
PORT
,NODE_ENV=production
, database connection strings, etc.).
- Deployment: The platform will automatically pull the code, execute the build (if configured), install dependencies, and run your application per the start command.
Platforms like Leapcell typically also provide features like log viewing, auto-scaling, custom domains, HTTPS, etc., allowing developers to focus more on business logic implementation rather than underlying server operations. This process is standardized for Express applications (whether using EJS or JSX views).
7. Conclusion
When performing server-side rendering in Express.js, EJS and JSX represent two different development paradigms. EJS still has its place in small projects due to its simplicity, but JSX, with its strong type safety (paired with TypeScript), componentization capabilities, and excellent development experience, is undoubtedly the better choice for building modern, robust, and maintainable web applications.
Although introducing JSX requires some additional configuration and understanding of the React ecosystem, its long-term benefits are significant. For teams looking to improve code quality and development efficiency, embracing JSX (TSX) for server-side rendering is a wise decision. Ultimately, regardless of the technology chosen, applications can be easily deployed to cloud platforms like Leapcell to enjoy modern application hosting services.
Leapcell: The Best of Serverless Web Hosting
Finally, recommend the best platform for deploying nodejs services: Leapcell