Today, it is impossible to imagine a modern large project without using Next.js or Nuxt.js.
But, nevertheless, if the task is to quickly create such a structure, then this method, which is described here, is perfect for this.
Today, we will create a small landing page application with 5 components that are located on the server.
Let's get started!
📦 Application structure
Our application will have a structure just like modern SSR applications (without BFF, of course, etc.), but the rendering will occur on the client, which is shown through the browser.
There is no concept of a database in our structure, since the data will be located in html files. But if we were doing registration on the landing page, we would have, say, a .json
file that would please modern databases, but this example should be done in 10 minutes, so there is no point in expanding the functionality.
Also, in order to connect the client to the server, we will connect a module such as HMPL:
👀 Where to start creating an app?
First of all, let's create two files global.css
and global.js
. They will include those styles and scripts that will not depend on what comes from the server.
global.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Roboto, sans-serif;
}
body {
line-height: 1.6;
color: #333;
}
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.section {
padding: 80px 0;
text-align: center;
}
.section h2 {
font-size: 36px;
margin-bottom: 30px;
}
global.js
console.log("Global scripts loaded");
As a result, it was possible not to connect global.js but, in general, it would be great for an example if we had common points on js. Config constants, utility functions, etc.
Now, we will create index.html, in which we will connect all the necessary modules for the landing page.
</span>
lang="en">
charset="UTF-8" />
name="viewport" content="width=device-width, initial-scale=1.0" />
My Landing Page
rel="stylesheet" href="global.css" />
<span class="na">src="https://unpkg.com/json5/dist/index.min.js">
<span class="na">src="https://unpkg.com/dompurify/dist/purify.min.js">
<span class="na">src="https://unpkg.com/hmpl-js/dist/hmpl.min.js">
<span class="na">src="global.js">
Enter fullscreen mode
Exit fullscreen mode
The site itself looks empty for now, so let's create our components!
⚙️ Server Configuration
For the server, we will take, of course, the Node.js platform. You can take any, it is not essential for the site. The framework will be Express.js.app.js
const express = require("express");
const path = require("path");
const bodyParser = require("body-parser");
const cors = require("cors");
const PORT = 8000;
const app = express();
const getRoutes = require("./routes/get");
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors({ origin: true, credentials: true }));
app.use(express.static(path.join(__dirname, "src")));
app.get("/", (_, res) => {
res.sendFile(path.join(__dirname, "src/index.html"));
});
app.use("/api", getRoutes);
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
Enter fullscreen mode
Exit fullscreen mode
routes/get.js
const express = require("express");
const expressRouter = express.Router();
const path = require("path");
const components = {
title: "CTA",
header: "Header",
features: "Features",
promo: "Promo",
cta: "CTA",
footer: "Footer",
};
Object.entries(components).forEach(([name, folder]) => {
expressRouter.get(`/get-${name}-component`, (_, res) => {
res.type("text/html");
res.sendFile(path.join(__dirname, `../components/${folder}/index.html`));
});
});
module.exports = expressRouter;
Enter fullscreen mode
Exit fullscreen mode
Having described just a couple of js files, we can now create our application parts in the components folder.The routes can be named anything, but for convenience I named them /api/get-${name}-component
⌨️ Writing the first component
Let's start with the banner, as this is our first content block on the landing page. We will do it directly from the server route at the URL http://localhost:8000/api/get-features-component.components/Features/index.html
id="features-component">
id="features" class="section features">
class="container">
Our Amazing Features
class="features-grid">
class="feature-card">
Fast
Lightning fast performance that saves you time.
class="feature-card">
Simple
Easy to use interface with no learning curve.
class="feature-card">
Reliable
99.9% uptime guaranteed for your business.
.features {
background: #f9f9f9;
padding: 80px 0;
text-align: center;
}
.features h2 {
font-size: 36px;
margin-bottom: 30px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 30px;
margin-top: 50px;
}
.feature-card {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
opacity: 0;
transform: translateY(20px);
transition: all 0.6s ease;
}
.feature-card h3 {
margin-bottom: 15px;
font-size: 22px;
}
@media (max-width: 768px) {
.features-grid {
grid-template-columns: 1fr;
}
}
const animateFeatures = function () {
const elements = document.querySelectorAll(
"#features-component .feature-card"
);
elements.forEach((element) => {
const elementPosition = element.getBoundingClientRect().top;
const screenPosition = window.innerHeight / 1.3;
if (elementPosition < screenPosition) {
element.style.opacity = "1";
element.style.transform = "translateY(0)";
}
});
};
window.addEventListener("load", animateFeatures);
window.addEventListener("scroll", animateFeatures);
Enter fullscreen mode
Exit fullscreen mode
Now, let's see what it will look like:Yes, the component on the server will not look very good, since we write styles that are intended only for it. But when we deploy all this things to our prod site, all the fonts and other things will be connected and the site will look fine.
✅ Let's finish writing the rest
Everything works for us and now we can finish writing all our components and connect them using HMPL to our index.html. We will also finish writing such components as:
Header: http://localhost:8000/api/get-header-component
Promo: http://localhost:8000/api/get-promo-component
CTA: http://localhost:8000/api/get-cta-component
Footer: http://localhost:8000/api/get-footer-component
You can find the whole list of them in the repository with this site, I will not just copy and paste the code here, because the article will be simply huge. The link to the repository can be found below.
🔗 Connecting everything to our site
Let's add the components of the server request to our html and append the resulting html.
</span>
lang="en">
charset="UTF-8" />
name="viewport" content="width=device-width, initial-scale=1.0" />
My Landing Page
rel="stylesheet" href="global.css" />
<span class="na">src="https://unpkg.com/json5/dist/index.min.js">
<span class="na">src="https://unpkg.com/dompurify/dist/purify.min.js">
<span class="na">src="https://unpkg.com/hmpl-js/dist/hmpl.min.js">
<span class="na">src="global.js">
const body = document.querySelector("body");
const template = `
{{#request src="http://localhost:8000/api/get-header-component"}}{{/request}}
{{#request src="http://localhost:8000/api/get-features-component"}}{{/request}}
{{#request src="http://localhost:8000/api/get-promo-component"}}{{/request}}
{{#request src="http://localhost:8000/api/get-cta-component"}}{{/request}}
{{#request src="http://localhost:8000/api/get-footer-component"}}{{/request}}
`;
const { response } = hmpl.compile(template)();
body.append(response);
Enter fullscreen mode
Exit fullscreen mode
It is worth noting that you can add loaders for our components, or for promo add interval requests via the interval attribute.
🖥️ Result
Now, let's see what we managed to put together in 10 (a little more) minutes of work:To me, it looks very nice, considering that there was no particular fuss about styles and such.
🖋️ Conclusion
In this article, we created a small, but very cool and functional application in literally 10 minutes, using the SSR method, only on the client. The same can be done with Next.js, but the very fact that we will have to connect the framework and completely depend on its structure, when here we connect one module and get the same thing (without indexing robots).Also, it would be great if you supported the project with your star! Thanks ❤️!💎 Star HMPL ★
🗄️ Repository link
You can find the full code here: https://github.com/hmpl-language/examples/tree/main/landingThank you all very much for reading the article!