Fast Web Apps: Advanced Optimization Techniques - Featured Image
Web development5 min read

Fast Web Apps: Advanced Optimization Techniques

We've all been there. You've spent months adding cool features to your web app, and suddenly you realize it's taking forever to load. What was once snappy now takes 10-30 seconds just for the initial render, and your users are probably closing the tab before they even see your masterpiece.

I recently faced this exact problem with a client project, and I wanted to share some practical solutions I discovered. These aren't just theoretical concepts, they're battle-tested approaches that actually work.

Implementing server-oriented methods using HMPL.js

Before diving into traditional optimization techniques, I want to highlight HMPL.js. Unlike other methods, this won't help with SEO (robots can't index the page), but it's incredibly flexible. You can plug it into literally any website: WordPress, Vue.js, Tilda, Next.js, without rebuilding everything from scratch.

Here's a quick example of how it works:

index.html
<main id="app"></main>

<script src="https://unpkg.com/json5/dist/index.js"></script>
<script src="https://unpkg.com/dompurify/dist/purify.min.js"></script>
<script src="https://unpkg.com/hmpl-js/dist/hmpl.min.js"></script>
client.js
const templateFn = hmpl.compile(
  `<div>
      <button data-action="increment" id="btn">Click!</button>
      <div>Clicks: {{ src: "/api/clicks", after: "click:#btn" }}</div>
  </div>`
);

const clicker = templateFn(({ request: { event } }) => ({
  body: JSON.stringify({ action: event.target.getAttribute("data-action") })
})).response;

document.querySelector("#app").append(clicker);

The beauty of this approach is its simplicity. You get rendered HTML without committing to a complex architecture. You can switch it on or off without consequences, and there's no steep learning curve, just a focused set of features. You can even remove the after attribute and load components during DOM rendering.

Check Out HMPL

Platform dependency

Before trying anything fancy, look at your platform's built-in optimization tools. Different frameworks need different approaches. What works for Next.js might not help with Vue or Angular.

Most modern frameworks have optimization features hidden in their configs. Simple tweaks like server response caching or image optimization might already be available, you just need to enable them. Read your framework's docs first; you might be surprised at what's already there.

From client-side to server-side rendering

One of the most effective ways to shrink your bundle is moving rendering from client to server. Instead of sending a giant JavaScript file that builds the page in the browser, you deliver pre-rendered HTML and minimal JS. Your page loads faster, and your bundle size drops dramatically.

Here's a simple example using Express and EJS:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= data.title %></title>
</head>
<body>
    <h1><%= data.title %></h1>
    <p><%= data.content %></p>
</body>
</html>
server.js
const express = require('express');
const app = express();
const PORT = 3000;

// Set EJS as the templating engine
app.set('view engine', 'ejs');

// Sample data
const data = {
    title: 'Server Side Rendering Example',
    content: 'This is an example of Server Side Rendering using Node.js and EJS.'
};

// Define a route
app.get('/', (req, res) => {
    // Render the HTML using EJS
    res.render('index', { data });
});

// Start the server
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

With EJS and Express, everything renders on the server. If you're starting a new project, frameworks like Next.js make this even easier, handling dynamic routes and SEO automatically.

But let's be honest, this approach has its downsides. Migrating an existing client-side app to SSR can be expensive and time-consuming. Plus, not every developer is familiar with server-side frameworks, so finding talent can be challenging.

That's where tools like HMPL.js come in handy. They give you server-side benefits without the full migration headache.

More ways to reduce your bundle

Beyond server rendering, here are five more practical techniques I've found effective:

Clean up your dependencies

I'm guilty of this one. When building features, I often try several packages, pick one, and forget to remove the others. Or worse, I import a massive library just to use one tiny function.

Want to find these bundle-bloating culprits? Try depcheck:

npm install depcheck
depcheck /path/to/my/project

Or simply:

npx depcheck

This tool might be old, but it still identifies unused dependencies. Just be careful, sometimes packages might seem unused but are actually required by other modules.

You can also use npm's built-in cleanup:

npm prune

This removes "extraneous" packages, cleaning up your node_modules folder.

Optimize your media files

(Image Source: ResourceGate)

I once worked on a site where a single video file was larger than the entire codebase. Don't be that person!

Images are especially easy to optimize. Modern compression tools can reduce file size by 60-70% with no visible quality loss. I've saved entire megabytes per image this way.

Converting from PNG/JPG to WebP can yield even better results. Most browsers support WebP now, and the size difference is dramatic.

Use CDNs for common libraries

Instead of bundling everything yourself, consider loading popular libraries from CDNs:

// Before
import { chunk } from "lodash";

// After
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

This approach offloads bandwidth to high-performance CDNs and leverages browser caching across sites.

Split your code

Dynamic imports are my favorite way to split code. Instead of loading everything upfront, load modules when needed:

// main.js
document.getElementById('loadButton').addEventListener('click', () => {
    import('./module.js')
        .then(module => {
            module.default();
        })
        .catch(err => {
            console.error('Error loading the module:', err);
        });
});

This way, module.js only loads when the user clicks the button.

For Webpack users, enabling chunk splitting is also helpful:

// webpack.config.js
module.exports = {
    optimization: {
        splitChunks: {
            chunks: 'all',
        },
    },
};

This automatically separates common code into shared chunks, reducing duplication.

Minify everything

Minification is an oldie but goodie. It strips whitespace, renames variables, and optimizes code patterns:

uglifyjs file.js -c toplevel,sequences=false

Most bundlers include minification by default, but double-check your settings to make sure it's enabled.

Conclusion

These aren't theoretical concepts, I've used every technique on real projects with significant results. I skipped obvious advice about DRY and KISS principles since you probably know them already. The key is finding the right balance for your specific project. Start with the easiest wins (like image optimization and minification), then move to more involved techniques if needed.

hassaankhan789@gmail.com

Frontend Web Developer

Posted by





Subscribe to our newsletter

Join 2,000+ subscribers

Stay in the loop with everything you need to know.

We care about your data in our privacy policy

Background shadow leftBackground shadow right

Have something to share?

Write on the platform and dummy copy content

Be Part of Something Big

Shifters, a developer-first community platform, is launching soon with all the features. Don't miss out on day one access. Join the waitlist: