A Collection of AI Tools for Various Purposes

This is just a collection of AI tools I’ve used that I’ve found useful. With so many AI tools sprouting up, this list will likely be updated regularly as time permits.

AI Portal

Image Editing

Audio Editing

Speech

Video Editing

Text Editing

  • ChatGPT
  • Grammarly

Code Editing

  • Cursor
  • Bolt.new
  • Bolt.diy
  • Claude 3.7 Code
  • GitHub Copilot
  • Coedium’s Windsurf
  • Locofy.ai
  • Vercel v0
  • Dora.ai
  • Relume
  • replit
  • lovable

Install AI Apps

Color Palette

Virtual Staging

Different Ways to Build a Component-Based Static Website

Static websites are fast and ideal for many types of websites like blogs, marketing websites, and more. When building websites, you should always use a component-based approach for code simplicity, maintenance, and reusability. Here are a few ways to build a static website using components.

  1. Web Components – No framework
  2. Eleventy (11ty) – Very simple and flexible static site generator that supports various template languages (Handlebars, Nunjucks, etc)
  3. Astro – Very similar to Eleventy, with the advantage of being able to load premade components from React, Vue, Svelte, etc.
  4. Svelte – More like React, but better. Outputs static files. Has native benefits like CSS optimizations and the ability to create interactive apps, if necessary.

The following video does a great job in comparing and demonstrating building a static site using web components and Svelte.

FrontLobby: Motivate Tenants to Pay Rent on Time and to Pay Their Rent Debt After Moving Out

I recently rented out an apartment to a new tenant. I created the lease online using Zillow. While Zillow does include mandatory notices and includes a credit score and background check service, it doesn’t mention anything about recommending landlords get certain personally identifying information (PII) that can be critical in helping landlords if a tenant decides to stop paying rent.

In one of my rentals, I inherited a tenant who, after a few years, couldn’t pay the rent. Despite trying to work with her, she clearly knew that she could stay just stop paying and stay in the apartment until she is served a formal eviction notice by the sheriff. Due to court backlogs, it would be about 2 months before the sheriff could serve her, and there was nothing I could do about it. I eventually lost around $5000, but at least I was able to renovate the apartment and rent it out for $500 a month more. Nevertheless, the carelessness of the tenant, who thought she could rip me off, was disturbing, as California law is too lenient on renters. Fortunately, I found a way that could either get the former tenant to pay her debt or suffer the consequences of her non-payment being recorded in her credit history. However, you can’t report your tenant to the credit bureaus unless you have enough information, including their date of birth. Since I didn’t have all that information, I had to do some investigative work using Instant Checkmate and Ancestry.com. With Instant Checkmate, I was able to find all sorts of information about my former tenant, including the fact that she had filed for bankruptcy and had been evicted in the past. Instant Checkmate looks like a super shady website, but luckily, it’s not. With Ancestry.com, I was able to get her date of birth, which is required in order to report people to the credit bureaus. With this information, along with signed copies of the lease and other details, I used a service called FrontLobby to go after my former tenant.

FrontLobby can be used to report current tenants to the credit bureaus when they pay late or don’t pay at all. It can also be used to report former tenants.

New / Existing Tenants

If you will have a new tenant, make sure you get the following information from them before they move in:

  • Legal first, middle, and last names
  • date of birth (required in order to report people to the credit bureaus
  • social security number (for more accurate reporting)
  • email
  • phone number
  • aliases, if any.

I would make a copy of their social security card and driver’s license.

You can then inform your new and existing tenants that you will use FrontLobby to track their rent payments and to automatically notify the credit bureaus if they are late. This should make tenants become more motivated to pay on time. You can also inform tenants that if they pay on time, that could help improve their credit score.

Former Tenants Who Are Delinquent

If your tenant has already moved out and they owe your rent, you can use FrontLobby to report their debt to the credit bureaus.

FrontLobby will first email them an introduction to encourage them to pay their debt.

FrontLobby will send the former tenant up to 3 introduction emails. If the former tenant doesn’t register with FrontLobby, FrontLobby will send monthly emails informing the former tenant that their credit score will continue to be affected until they pay.

Lastly, if the former tenant still doesn’t pay, then they will get an email alerting them that their debt has been registered with the credit bureaus.

Once you activate debt reporting, you’ll see a notice like this:

Using a Grid System for Web Design and Development

I’ve worked with many graphic designers who were tasked with created web designs. Unfortunately, most designers claimed to know web design when in reality they didn’t. This was obvious when they kept asking me for specific width and height dimensions when I’d ask them for a new hero design. Designing for print is much simpler than designing for web. With print, what you see is what you get because there’s only one size and nothing is interactive. In fact, there are numerous other factors that must be considered when designing for web, including SEO impact. In this post, I’ll talk about just one aspect of design that all web designers should understand: grid systems.

A grid system is just a bunch of columns and, optionally, rows, that help you arrange your design elements. It’s useful for print design, but I’d argue it is essential for web design. In the New York Times screenshot above, you see a bunch of pink columns separate by white gaps (gutters). Notice how the content blocks fit within the columns. Without those columns, your design could end up with a lot of alignment issues, especially when the design contains a lot more than just a bunch of text blocks. Grid systems don’t just make it easier for designers to align content – they also make it easier for developers to develop responsive pages that match the designs they are provided. That’s why CSS has a display option called “grid” and why Bootstrap, one of the most popular CSS frameworks, offers a grid system with sensible defaults.

The most common grid system is the 12-column grid. If you’re a developer, you don’t have to use CSS grid or a premade system like Bootstrap’s grid system – you can use flexbox to lay out your content. But I personally think it’s better to use a grid for your main layout and just use flex for sub-layouts, e.g., when you’re laying out content within a grid’s cell. This is particularly helpful because your main layout will need to be responsive, and Bootstrap’s grid system already includes code to make your grid responsive automatically. Additionally, if you are part of a dev team, it’ll be easier to update someone else’s code if everyone follows the same coding convention, like using Bootstrap’s grid classes.

If you use Tailwind CSS, you can easily create a grid system using Tailwind CSS classes. However, you’ll have to define your own breakpoints, e.g., on desktop, show 12 columns, but on mobile, show only one.

The easiest way to demonstrate both Bootstrap’s grid system and creating a grid in Tailwind CSS is by example. If you are a developer, the CodePen below should be self-explanatory. Open each CodePen in a separate tab to see the columns on desktop.

Tailwind CSS

See the Pen Tailwind CSS Layouts by Abdullah Yahya (@javanigus) on CodePen.

Bootstrap

See the Pen Untitled by Abdullah Yahya (@javanigus) on CodePen.

Set Up a Component-based Static Site Generator That Uses JSON Files to Store Page Content Instead of a CMS

There are many options for a content management system (CMS). WordPress is the most popular one. Its native WYSIWYG block editor CMS is called Gutenberg. I’m using it right now as I’m typing these words 🙂 WordPress is more than just a CMS, it’s a platform that you can heavily customize and that you can add additional WYSIWYG CMSs on top of it, e.g. WP Bakery, Elementor, and many others. You can even create a non-WYSIWYG CMS in WordPress using Advanced Custom Fields (ACF), which would give you something similar to dedicated non-WYSIWYG CMSs like Contentful. Aside from WordPress, there’s a plethora of WYSIWYG CMSs like Webflow, Wix, etc. Then there are headless CMSs like Contentful, Strapi, Directus, and many more. WordPress can even be used as a headless CMS because it has an API from which you can get all content. This post will share a simple alternative to these complex CMS solutions by using a static site generator (SSG) called 11ty along with JSON files to store content in what you can consider a poor man’s CMS. Why? Because

  1. Faster updates
    CMSs, whether WYSIWYG or not, are for non-technical users (like most marketing people). I’ve worked in marketing for 14 years with many, many marketers. The vast majority of them do not want to update a website themselves. So, if developers have to update the website, there’s no need for a CMS. As a developer myself, the abstraction layer that a CMS provides just slows me down. Elementor, for example, is one of the most popular WYSIWYG CMSs. It doesn’t give you the ability to edit the raw code. Everything must be done visually. That can slow me down if I can’t easily find how to do what I need using the Elementor UI.
  2. Security
    WordPress is known for being vulnerable to hacks, not necessarily because of WordPress itself but because of the many plugins that people install in them. Static websites are more secure than any dynamic website.
  3. Performance
    WordPress websites are dynamic (content is queried from a database and PHP files must be processed on the server before they are delivered to clients). Static websites, on the other hand, require no processing. Of course, caching can improve the performance of dynamic sites, but static sites are still faster.
  4. Simplicity
    WordPress is way more complex than a simple static site, including a static site built by a static site generator like Eleventy. If you add a CMS on top of WordPress, then it becomes even more complex. If you use a static site generator along with a headless CMS like Contentful, then you have another dependency and layer of complexity due to needing to set up content models and then fetch them using APIs. The setup I will show you in this post will allow developers to see their entire website structure in a familiar file system without any CMS UI to get in their way. No CMS will alter code in any unintended way.
  5. Lower learning curve
    If developers have to update a website instead of non-technical marketers, then a static website offers a lower learning curve because all developers already understand website code. On the other hand, not every developer is fluent in WordPress or one of the many CMS plugins it offers, along with whatever customizations may have been made, so it would take them longer to learn these CMSs.
  6. Cost
    The setup I’m using is free because 11ty is free (open source), GitHub is free, and Netlify (for web hosting) offers a free plan. While you can host WordPress for free and use a free open-source CMS, you’ll have to maintain them, which you probably don’t want to do. Many companies go with a managed WordPress host like WP Engine, but they can be somewhat expensive. The Contentful CMS can also be expensive depending on the number of users and records.

Rather than explain how I created this starter website that uses Eleventy + JSON, I will just explain how the code, which is in this GitHub repo, works. If you want to learn how to set up an Eleventy website, just read the 11ty docs. It’s very simple.

Let’s go!

1. Install NodeJS

https://nodejs.org/en/download

2. Install Git

https://git-scm.com/downloads

3. Set Up a New Website Project Folder

mkdir test-website
cd test-website

4. Clone This Git Repo

git clone https://github.com/javanigus/eleventy-json-starter.git

5. Install Dependencies

npm install 

6. Run Eleventy

npm start

11ty will start a local web server. Open the localhost URL and view the starter site on your machine.

The top portion shows the starter site with a few menu links and some body content. At the bottom is a dump of all variables available to the page template, e.g. home page, about page, etc.

File Structure

  • “dist”, short for “distribution”, is the build output folder.
  • “src”, short for “source”, is where your source code goes
  • .eleventy.js is the Eleventy configuration file
  • the other files are self-explanatory

Eleventy Configuration

The Eleventy config file is .eleventy.js. I’ve tried to keep it as simple as possible. The fewer dependencies, the less the maintenance and the fewer the things that could break. Now, I personally would add PostCSS and Tailwind CSS to this setup if I were using it for work, but that’s beyond the scope of this starter site.

This code basically

  • tells Eleventy which file types to copy from the src folder to the dist folder
  • adds some debugging capabilities
  • tells Eleventy which folder is the source (“src”) and build output (“dist”)
  • tells Eleventy which folder it uses for includes / partials / components (“_includes”)
  • tells Eleventy which folder it uses for layouts (“_layouts”). To keep this starter simple, I’m not using any layouts.
module.exports = function(eleventyConfig) {
    const inspect = require("util").inspect;
	
    eleventyConfig.addPassthroughCopy("src", {
		//debug: true,
		filter: [
			"404.html",
			"**/*.css",
			"**/*.js",
			"**/*.json",
			"!**/*.11ty.js",
			"!**/*.11tydata.js",
            "!**/*.11tydata.json",
		]
	});
  
	// Copy img folder
	eleventyConfig.addPassthroughCopy("src/img");

	eleventyConfig.setServerPassthroughCopyBehavior("copy");

    eleventyConfig.addFilter("debug", (content) => `<pre>${inspect(content)}</pre>`);

	// tell 11ty which files to process and which files to copy while maintaining directory structure
	// eleventyConfig.setTemplateFormats(["md","html","njk"]);

	return {
		dir: {
			input: "src",
			output: "dist",
			// ⚠️ These values are both relative to your input directory.
			includes: "_includes",
			layouts: "_layouts",
		}
	}
};

Website Source Files (src folder)

The “src” folder is where you put your website files (HTML, CSS, JS, etc). Instead of HTML files, I’m using Nunjucks files (“njk” extension). You can use other templating languages like Handlebars, but I prefer Nunjucks. You can think of Nunjucks as a simple version of PHP. It allows you to add logic and loops and output variables.

_data folder

The _data folder contains global data, whether it’s data returned from a JavaScript file (data.js) or from JSON files (data.json, pressReleases.json). This data is available to all page templates.

_includes folder

The _includes folder is where I put shared code (header.njk, footer.njk) and components (section1.njk, section2.njk). Section 1 can be a hero component and section 2 can be a features component, for example.

css folder

The css folder contains sitewide CSS (global.css) and CSS for each component (header.css, section1.css, etc).

js folder

The js folder contains sitewide JavaScript (global.js) and JavaScript for each component (header.js, section1.js, etc).

Other files and folders

The other files and folders in the “src” folder correspond to each page on the site,

  • src/index.njk (home page at /)
  • src/product1/index.njk (product page at /product1)
  • src/product1/support/index.njk (product support page at /product1/support/)
  • etc

Home Page

Home Page-specific files

  • src/index.njk (you can think of this as index.php)
  • src/index.css (this CSS file is only needed if you have CSS that is exclusive to the home page)
  • src/index.js (this JS file is only needed if you have JS that is exclusive to the home page)
  • src/index.data.json (this is the JSON file that contains all component data that is used in the page)
---js
{
  variable1: "value1",
  eleventyComputed: {
    datum(data) {
      return data;
    }
  }
}
---

<html>
<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/3.0.1/modern-normalize.min.css" integrity="sha512-q6WgHqiHlKyOqslT/lgBgodhd03Wp4BEqKeW6nNtlOY4quzyG3VoQKFrieaCeSnuVseNKRGpGeDU3qPmabCANg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <link rel="stylesheet" href="/css/global.css" />
    <link rel="stylesheet" href="/css/header.css" />
    <link rel="stylesheet" href="/css/footer.css" />
    <link rel="stylesheet" href="/css/section1.css" />
    <link rel="stylesheet" href="/css/section2.css" />
    <link rel="stylesheet" href="/index.css" />
</head>
<body>
    {% include "header.njk" %}

    {% include "section1.njk" %}

    {% include "section2.njk" %}

    <section>
        <p class="localCss">Test local CSS file</p>
    </section>
    
    {% include "footer.njk" %}

    <script src="/js/global.js"></script>
    <script src="/js/header.js"></script>
    <script src="/js/footer.js"></script>
    <script src="/js/section1.js"></script>
    <script src="/js/section2.js"></script>
    <script src="/index.js"></script>
</body>
</html>

<!-- for debugging data -->
<div style="padding: 1em;">
<h2>Dump of all data</h2>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><code>{{ datum | debug }}</code></pre>
</div>

The front matter at the top above the <html> tag is just some code to help with debugging. It goes with the debugging code block at the bottom. Together, this dumps all variables, including global data, to the bottom of the page in the browser.

In the <body> section, I’m including 4 components

  • header.njk
  • section1.njk
  • section2.njk
  • footer.njk

You can think of these as including PHP files in another PHP file. Instead of PHP, it’s Nunjucks.

In the <head> section, I’m including all CSS that I need, including the CSS for the components that I’m using (header.css, section1.css, etc).

At the bottom of the <body> section, I’m doing the same thing for JavaScript.

Now, you might be thinking that this approach could load a bunch of individual CSS and JS files. As is, it would, but you can easily just add a step to your build system to optimize (bundle and minify) all CSS and JS files in just 2 files, one for all CSS and one for all JS. That’s beyond the scope of this post.

So far, this should be straightforward. Before we look at the index.data.json file, let’s look at the components that the home page is using (section1.njk and section2.njk). For demo purposes, I kept them simple.

section1.njk

<section id="section1">
    <h1>Welcome. {{section1.title}}</h1>
</section>

This should be self-explanatory. You’re just outputting a variable just like you would in PHP.

section2.njk

<section id="section2">
    <h2 {% if section2.textColor %} style="color: {{ section2.textColor }};" {% endif %}>Features</h2>
    <ul>
        {% for feature in section2.features %}
            <li><a href="{{feature.link}}">{{ feature.text }}</a></li>
        {% endfor %}
    </ul>
</section>

This section demonstrates the use of conditional logic and looping.

Looking at the 2 components, we know what variables exist, so we can write our JSON data file with corresponding values.

index.data.json

{
    "section1": {
        "title": "This is the home page."
    },
    "section2": {
        "textColor": "red",
        "features": [
               {
                    "text": "Feature 1",
                    "link": "/forms/vmdr/"
               },
               {
                    "text": "Feature 2",
                    "link": "/forms/pm/"
               },
               {
                    "text": "Feature 3",
                    "link": "/forms/cmdb/"
               }
           ]
	}
}

The JSON file contains an object for each component (section1 and section2) along with variables for each.

Building Pages

If Eleventy is running, it will detect any file saves and rebuild, e.g.

Notice how all static HTML files were built and put in the output folder (“dist”). When Eleventy builds each page, it takes all available data (global data from the “_data” folder and local page-specific data (e.g. from index.data.json) and evaluates all includes (components).

The /src/product1/ and /src/product1/support/ pages are very similar to the home page. The /src/press-releases/ folder is different in that it uses pagination to generate multiple pages from a single template. Let’s look at that in more detail.

Press Releases

The relevant files are

  • _data/pressReleases.json (data file for all press releases)
  • src/press-releases/index-detail.njk (file that generates all individual press releases)
  • src/press-releases/index.njk (file that generates a listing page with links to all press releases)

_data/pressReleases.json

This file is an array of JSON objects. Each object contains data for one press release. Note that one key is “slug”. It will be used to generate the URL for the press release.

[
	{
		"title": "Press release 1",
		"slug": "press-release-1",
        "body": "This is the body of the press release 1."
	},
	{
		"title": "Press release 2",
		"slug": "press-release-2",
        "body": "This is the body of the press release 2."
	},
	{
		"title": "Press release 3",
		"slug": "press-release-3",
        "body": "This is the body of the press release 3."
	},
	{
		"title": "Press release 4",
		"slug": "press-release-4",
        "body": "This is the body of the press release 4."
	}
]

src/press-releases/index-detail.njk

This file will generate a bunch of individual press release pages that will look like this

In this file, we have some front matter that tells Eleventy to paginate (create multiple page) from the data in the “pressReleases” variable (which is from _data/pressReleases.json). The “permalink” contains the “slug” variable. It tells Eleventy the path to use as it iterates to generate each press release page.

In the <body> section, we’re just outputting the press release title and body using variables.

---
pagination:
  data: pressReleases
  size: 1
  alias: pressRelease
permalink: "press-releases/{{ pressRelease.slug | slugify }}/"
---

<html>
<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/3.0.1/modern-normalize.min.css" integrity="sha512-q6WgHqiHlKyOqslT/lgBgodhd03Wp4BEqKeW6nNtlOY4quzyG3VoQKFrieaCeSnuVseNKRGpGeDU3qPmabCANg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <link rel="stylesheet" href="/css/global.css" />
    <link rel="stylesheet" href="/css/header.css" />
    <link rel="stylesheet" href="/css/footer.css" />
</head>
<body>
    {% include "header.njk" %}

    <section>
        <h1>{{ pressRelease.title }}</h1>
        <p>{{ pressRelease.body }} </p>
    </section>
        
    {% include "footer.njk" %}

    <script src="/js/global.js"></script>
    <script src="/js/header.js"></script>
    <script src="/js/footer.js"></script>
</body>
</html>

src/press-releases/index.njk

In this file, we want to list all press releases with a link to each one so the page looks like this

So, we loop over the pressReleases array of JSON objects to do so.

---js
{
  variable1: "value1",
  eleventyComputed: {
    datum(data) {
      return data;
    }
  }
}
---

<html>
<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/3.0.1/modern-normalize.min.css" integrity="sha512-q6WgHqiHlKyOqslT/lgBgodhd03Wp4BEqKeW6nNtlOY4quzyG3VoQKFrieaCeSnuVseNKRGpGeDU3qPmabCANg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <link rel="stylesheet" href="/css/global.css" />
    <link rel="stylesheet" href="/css/header.css" />
    <link rel="stylesheet" href="/css/footer.css" />
</head>
<body>
    {% include "header.njk" %}

    <section>
        <h1>Listing of all press releases</h1>
        <ul>
            {% for pressRelease in pressReleases %}
                <li><a href="/press-releases/{{pressRelease.slug | slugify}}">{{ pressRelease.title }}</a></li>
            {% endfor %}
        </ul>
    </section>
        
    {% include "footer.njk" %}

    <script src="/js/global.js"></script>
    <script src="/js/header.js"></script>
    <script src="/js/footer.js"></script>
</body>
</html>

<!-- for debugging data -->
<div style="padding: 1em;">
<h2>Dump of all data</h2>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><code>{{ datum | debug }}</code></pre>
</div>

When you save a file, Eleventy will build all pages. As you can see in the screenshot below, Eleventy created 4 press release pages, one for each JSON object in the JSON data file.

  • [11ty] Writing ./dist/press-releases/press-release-1/index.html from ./src/press-releases/index-detail.njk
  • [11ty] Writing ./dist/press-releases/press-release-2/index.html from ./src/press-releases/index-detail.njk
  • [11ty] Writing ./dist/press-releases/press-release-3/index.html from ./src/press-releases/index-detail.njk
  • [11ty] Writing ./dist/press-releases/press-release-4/index.html from ./src/press-releases/index-detail.njk

Eleventy also built the listing page.

  • [11ty] Writing ./dist/press-releases/index.html from ./src/press-releases/index.njk

Hosting

If you use Netlify or Vercel for hosting, you can connect them to GitHub so that whenever you push to GitHub, each will trigger a build and deploy your changes to production on a global CDN.

Updating JSON Files Reliably

Since content will be in JSON files, you may wonder how easy it would be to edit them without breaking the JSON format. If this is your concern, you can always just copy and paste the JSON code into an online JSON editor like this one. On the left is the JSON content in “code” format and on the right is the same content in “tree” format. In the “tree” format, you can conveniently expand and collapse nodes (in case some are too long) and safely edit the name/value pairs without worrying about breaking the JSON format. If the “Live” toggle is enabled, you can see your changes in both panes updated automatically. When you’re done editing in “tree” view, you can just copy/paste the code in “code” view back to your code editor.

If you need to put HTML in a JSON value, you’ll need to escape the HTML first. you can use an online tool like this one to do that. Just paste the HTML in the top field, click “Escape JSON”, and get the escaped HTML in the bottom field.

If you need an easy way to get HTML from a visual text editor like MS Word or a Google Doc, you can use EditorHTMLOnline. Just type your content on the left and then copy the HTML on the right.

Here’s an example.

If you want to give users a simple web form with validation and select fields, you can use JSON editor.

Conclusion

Now, you can create components using simple HTML, CSS, JS, and Nunjucks (which is similar to JavaScript) and you can store all your data in simple JSON data files rather than some database or external headless CMS. The entire system is super simple but effective without a very low learning curve and zero abstraction.

Bolt: Save Time Coding Using This Agentic AI Tool

Coding web pages by hand is time-consuming. I’ve tried a few AI-based coding tools like Claude.ai, Ninja AI, and Bolt. Bolt seemed to produce the best results. It’s not perfect, but it definitely can serve as a good starting point. To demonstrate, let’s see how each of these AI tools generate code for this simple section.

For each tool, I’ll upload the same screenshot of the section and provide the same prompt, namely:

Write plain HTML and Tailwind CSS code to create the uploaded screenshot exactly.

Claude.ai (using Claude 3.5 Sonnet)

Here’s the output.

Claude can’t show a preview, so I copied and pasted the code into Codepen. Here’s how it looked.

That’s actually not bad. The image is missing because it’s a placeholder image to a relative path that doesn’t exist.

Ninja AI

For the models, I chose Claude 3.5 Sonnet for the external model. Ninja AI will combine it with its own internal models. Here’s the input.

And here’s the output.

Like Claude, Ninja AI can’t show me a preview, so I copied and pasted the code into CodePen. Here’s what it showed.

Not bad, but it’s not as good as Claude even though I chose Claude as the external model. The main issue is the vertical spacing between the elements on the right.

Bolt.new

Here’s the input.

Bolt can show a visual of what the code would produce. Here’s the code output.

Note that Bold will install a Vite and a bunch of dependencies like Tailwind CSS, Autoprefixer, PostCSS, etc. Here’s the visual preview output.

Conclusion

I’ve run a bunch of other tests comparing all 3 AI tools. Bolt is better than the other tool for code generation.

Bolt.diy

The problem with all of the above AI coding tools is they can become expensive. Luckily, there’s an open-source version of Bolt called Bolt.diy. It can be used with any LLM, including the free, experimental version of Google Gemini Pro 2.0 and DeepSeek. You can install bolt.diy by following the simple instructions at https://github.com/stackblitz-labs/bolt.diy. When you run bolt.diy, it will open in a local browser.

Let’s try a couple of LLMs with bolt.diy to code the same section above.

Google Gemini Pro 2.0 Experimental

To use Google Gemini Pro 2.0 Experimental, you’ll need to get an API key. Go to OpenRouter.ai, search for the LLM, and get a free API key.

Here’s the input.

While writing the code, bolt.diy returned an error.

I clicked “Ask Bolt”, it Bolt self-corrected. Here’s the code output.

And here’s the visual preview.

This does not look good at all. Let’s try DeepSeek Coder.

DeepSeek Coder

We’ll need an API key. Go to the DeepSeek platform, sign up, and get a key.

Here’s the input in bolt.diy with DeepSeek selected.

And here’s the output.

Pinegrow: Save Time Coding With This Low-Code Editor

Some of the things that consume too much time as a web developer are manually typing HTML and CSS and looking up documentation. For example, when creating a list, it takes much longer to type <ul><li></li>….</ul> than it is to just click a button and start typing the content like you do in MS Word or Google Docs. Another example is when I don’t remember the syntax for a Tailwind CSS class and I have to look it up in the online documentation. After searching for a low-code editor that allows me to have both a WYSIWYG editor alongside a code editor alongside a list of controls, I have only found one that meets that criteria. Pinegrow offers both a desktop and a web-based low-code editor that supports plain HTML/CSS/JS, Tailwind CSS, Bootstrap, and much more. For now, just using it for plain HTML/CSS and Tailwind CSS saves me a lot of time. Following is a screenshot of how I have the UI.

Due to the large amount of information and my preferences for not having to scroll a lot, I expand the window full size on a 32″ 4K monitor. The screenshot above shows the following panes:

  • top left = WYSIWYG editor
  • bottom left = code editor
  • middle = element properties
  • right = DOM tree

You can edit code and see the changes in the visual editor. You can also insert elements into the visual editor and edit text visually. When you click on an element in the visual editor or the DOM tree, you can edit its properties using the various controls in the middle pane. For example, if I want to add bottom margin to an element, I don’t have to remember the possible Tailwind CSS preset values. Instead, I can just click a dropdown and choose a value. As I hover over the various dropdown values, I can visually see the margin change size. This is much easier than trying different values in a code editor and then reloading your browser to see the change. If I want to enter a custom value, I just type it in the field and choose a unit (px, em, etc).

When you want to insert an element, e.g. a list, just drag the corresponding button in the “+ Insert” dropdown over to the location in the visual editor where you want to place the new element.

Editing an element’s properties is super easy thanks to the complete controls with pre-populated Tailwind CSS values.

For example, if I want to vertically or horizontally align an element in a flexbox or CSS grid container, I can just visually see which button depicts the alignment I want and then click on it. Pinegrow will automatically update the code and the visual preview.

This is so much easier than typing “items-stretch”, “items-center”, “items-start”, etc.

If you’re having a hard time selecting an element in the visual preview, just click on it in the DOM tree. You can then edit the element’s properties in the middle pane.

If you are using the online version of Pinegrow and you want to export your code, just copy it from the code editor into your other editor (I use VS Code). Or, you can use the desktop version of Pinegrow and edit your local files directly.

Set Up a Simple and Reliable Static Site Generator Using 11ty (Eleventy) + Tailwind CSS

1. Install NodeJS

https://nodejs.org/en/download

2. Install Git

https://git-scm.com/downloads

3. Set Up a New Website Project Folder

mkdir test-website
cd test-website

4. Create a package.json file

npm init -y

5. Install Eleventy (11ty) Static Site Generator

npm install @11ty/eleventy

6. Verify Eleventy Runs

npx @11ty/eleventy

7. Install Tailwind and Tailwind CLI

npm install tailwindcss @tailwindcss/cli

Tailwind CSS will compile Tailwind CSS classes to CSS and Tailwind CLI will allow us to run Tailwind CSS from the command line.

https://tailwindcss.com/docs/installation/tailwind-cli

8. Create a Tailwind Config File

Create a file called tailwind.config.js in the project root and put the following started config. The “content” key tells Tailwind CSS which files to process.

export default {
    theme: {
      extend: {
        colors: {
          'primary': '#ff49db',
        },
        fontFamily: {
          'sans': ['Helvetica', 'Arial', 'sans-serif'],
        },
      },
    },
    plugins: [],
    content: ["./src/**/*.{njk,md,html}", "./src/**/*.svg",]
  }

9. Tailwind CSS Input and Output Files

Tailwind CSS will also process input files and output the results where we want. There are 2 Tailwind inputs:

  1. a CSS input file
  2. Tailwind CSS classes in HTML

Let’s put the Tailwind CSS input file at /src/css/tailwind-input.css and let’s have Tailwind CSS put the output file at /dist/src/tailwind-output.css.

10. Eleventy Input and Output Folders and Files

By default, Eleventy will build source files that are in the “src” folder and output them to a “_site” folder. If you want the output to go to a different folder, create an Eleventy config file and specify the output folder name there. By default, Eleventy will not copy static assets like CSS, JS and images to the output folder. Create an Eleventy config file (.eleventy.js) in the project root and add the following code to

  1. tell Eleventy to copy certain files to the output folder (note that we are telling Eleventy to not copy the source Tailwind CSS input file)
  2. tell Eleventy to put the output in a folder called “dist”
module.exports = function(eleventyConfig) {
	eleventyConfig.addPassthroughCopy("src", {
		//debug: true,
		filter: [
			"404.html",
			"**/*.css",
            "!**/*tailwind-input.css",
			"**/*.js",
			"**/*.json",
			"!**/*.11ty.js",
			"!**/*.11tydata.js",
		]
	});
  
	// Copy img folder
	eleventyConfig.addPassthroughCopy("src/img");

	eleventyConfig.setServerPassthroughCopyBehavior("copy");

	// tell 11ty which files to process and which files to copy while maintaining directory structure
	// eleventyConfig.setTemplateFormats(["md","html","njk"]);

	return {
		dir: {
			input: "src",
			output: "dist",
			// ⚠️ These values are both relative to your input directory.
			includes: "_includes",
			layouts: "_layouts",
		}
	}
};

11. Install npm-run-all

In order to have Eleventy build static pages AND process Tailwind CSS, we need to run both Eleventy and Tailwind CSS in parallel. To do that, install npm-run-all.

npm install npm-run-all --save-dev

12. Update Scripts

To run Eleventy and Tailwind CSS concurrently, let’s add some scripts. Open package.json and add the following scripts.

"start": "npm-run-all -p dev:*",
"build": "run-s build:*",
"dev:11ty": "eleventy --serve",
"dev:css": "tailwindcss -i src/css/tailwind-input.css -o dist/css/tailwind-output.css --watch --postcss", 
"build:11ty": "eleventy",
"build:css": "tailwindcss -i src/css/tailwind-input.css -o dist/css/tailwind-output.css --postcss"

The tailwindcss line has a reference to the input Tailwind CSS file (src/css/tailwind.css) and where the output CSS would go (dist/css/tailwind.css).

Your package.json file should look like this.

13. Create a “src” folder

The src folder will contain your source website files. Your folder structure should look like this now.

14. Create some files

Here’s an example folder structure for your website files.

The source files are all in the “src” folder. The “img” folder is where images can go and the “js” folder is where JavaScript files can go.

Tailwind CSS

In the “css” folder, there is a tailwind-input.css file. To start, just add this line to it.

@import "tailwindcss";

Non-Tailwind CSS (Optional)

The “css” folder can optionally contain non-Tailwind CSS files.

  • The CSS files in a “partials” folder group CSS by component, e.g. button.css, header.csss, etc.
  • The global.css file can contain CSS that applies to all pages on the site.
  • The index.css file can contain CSS that applies to just one or a few pages, e.g. the home page.

In this example, there is only one page – the home page (index.html) in the document root.

15. Write Code

Here is example code for the home page. Note the way the local CSS and JS files are referenced.

Tailwind CSS will compile the input Tailwind CSS file (/src/css/tailwind-input.css) and the Tailwind CSS classes in the HTML (e.g. on lines 18 and 19 of index.html) and output it at /dist/css/tailwind-output.css. That’s why on line 12 there is a reference to the path to the Tailwind output file.

16. Run Eleventy (and Tailwind CSS)

npm start

You will see a “dist” folder get created along with the css/tailwind-output.css file.

Eleventy will launch a local web server at http://localhost:8080/. Go to that URL to view the site and verify the site looks as correct.

17. Add Git

Put the project folder in version control by running

git init

Then, create a .gitignore file in the project root and enter the names of folders you want to exclude from version control, like these folders:

node_modules
dist
.cache

Add and commit all files to version control

git add *
git commit -m "first commit"

18. Set Up SSH Keys to Publish to GitHub

https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent

If you are on Windows, use Git Bash, which by default is located at C:\Program Files\Git\git-bash.exe.

19. Push Files to GitHub

  1. create a new repo in GitHub (mine is called eleventy-tailwind)
  2. push your files to GitHub
git remote add origin git@github.com:javanigus/eleventy-tailwind.git
git branch -M main
git push -u origin main

20. Publish to GitHub Pages

To publish your static site to GitHub Pages, install gh-pages.

npm install gh-pages –save-dev

Update package.json to include

"deploy": "gh-pages -d dist"

Deploy by running

npm run deploy

Your site will be live at https://<username>.github.io/<repository>/.

Optional

Add PostCSS, Autoprefixer, and other PostCSS plugins to further optimize your build output.

Get Started with Elementor Page Builder for WordPress

Initial WordPress Setup

Once you’re logged in to WordPress,

  1. delete all posts (move to trash and empty trash)
  2. delete all pages (move to trash and empty trash)

Purchase Elementor Pro

Go to Elementor.com and buy a license for the Elementor Pro website builder plugin.

Once you’ve paid, click the Download button to download Elementor Pro.

Install Hello Theme

In WordPress,

  1. go to Appearance > Themes
  2. click “Add New Theme”
  3. search for “Hello Elementor”
  4. hover over it and click “Install”
  5. when installation is complete, click the “Activate” button

You will see a message offering to install the free version of Elementor. Click “Install Elementor” and then click “Activate Plugin”.

Install Elementor Pro

Go to Plugins. You will see the free version of the Elementor plugin installed.

Click “Add New Plugin” > “Upload Plugin”, choose the Elementor zip file you download previously, then click “Install Now”.

Click “Activate Plugin”.

In the list of plugins, click “Connect & Activate”.

You will taken to Elementor’s website to log in.

Click “Activate my license”. Once activated, you will see…

If you visit your website, it will look similar to this.

Delete All Other Themes

Go to Appearance > Themes. For each theme besides “Hello Elementor”, click “Theme Details” > “Delete”.

WP Admin > Elementor > Settings

Now that Elementor has been activated, you’ll see 2 new menu items in the WordPress admin: “Elementor” and “Templates”, each with submenus. Click on Element > Settings to optionally change any general settings, integrations, performance settings, and features.

To create mega menus, go to Elementor > Settings > Features and beside “Menu”, select “Active”. Scroll down and click “Save”.

WP Admin > Elementor > Submissions

When someone submits a form, Elementor will store form submissions here.

WP Admin > Elementor > Custom Fonts

You can upload some custom fonts here to use in your website. For example, you can browse free fonts on Google Fonts, download them, and then upload them here.

WP Admin > Elementor > Custom Icons

You can upload custom icon sets here from Fontello, IcoMoon, and Fontastic as zip files to use in your website.

WP Admin > Elementor > Custom Code

You can add custom code like pixels, meta tags and any other scripts to your site from here.

WP Admin > Elementor > Role Manager

The Role Manager lets you specify what your users can edit in Elementor.

WP Admin > Elementor > Element Manager

The Element Manager lets you enable/disable various Elementor, Elementor Pro, and native WordPress widgets when you are building pages with the Elementor page builder.

WP Admin > Elementor > Tools

Under Tools, you can do various things like regenerate CSS and data files, replace URLs, roll back to an older version of Elementor, put your site in maintenance mode, and import/export a template kit.

WP Admin > Elementor > System Info

Under System Info, you see details of your server environment, WordPress environment, theme, user, active plug-ins, must-use plugins, features, integrations, Elementor experiments, error log, and more.

WP Admin > Appearance > Customize > Site Identity

To add your logo and favicon to your website, go to Appearance > Customize > Site Identity and then click the “Select Logo” button and the “Select Site Icon”, respectively. You can also enter a name and tagline for your website. For demo purposes, I’ll enter “ABC Company” and leave the tagline blank.

WP Admin > Appearance > Customize > Homepage Settings

Since WordPress started as a CMS to create blogs, the default home page was an index of blog posts. Since we want our home page to be a custom page, let’s change the home page type to “static”. Go to Appearances > Customize > Homepage Settings and choose “A static page”. For “Homepage”, since we haven’t created one yet, add a new one titled “Home” and click the “Add” button. You can leave “Posts page” empty for now.

WP Admin > Appearance > Customize > Menu

Decide what pages you want linked from your website’s main menu. You can have a multi-level menu, e.g. top-level links with a drop-down on hover to show a submenu of links. For demo purposes, let’s say we want our menu to be as follows:

  • Products
    • Product A
    • Product B
    • Product C
  • Services
    • Service A
    • Service B
    • Service C
  • About
  • Contact

To create this menu, go to Appearance > Customize > Menus > Create New Menu. Then, give the menu a name, e.g. Header Menu, and choose a location.

Click Next > Add Items and start adding the names of the links under “Pages”. You can then drag menu items to nest them the way you want to create submenus.

You can see a live preview of the menu in the Hello Elementor starter theme we’re using. Click “Publish” to save your changes.

Note that you can create multiple menus, e.g. one for the header and one for the footer.

Edit Home Page

When you click “Pages” in the WordPress admin panel, you’ll see a list of pages you created when you added menu items. Note that the “Home” page is labeled as the “Front Page”.

That’s why when you click on a menu item, a page that uses the “page” template will appear with a page title that matches the menu item text. For example, when I click “Product A” in the menu, I see this.

If you click on “Home” in the list of pages, you’ll see the WordPress Gutenberg block editor showing the page title “Home”.

We don’t want to edit the page using the native WordPress editor. Click the blue “Edit with Elementor” button at the top. We’ll be taken to the same page but in Elementor, which is a much better WYSIWYG editor.

If you need to go back to the WordPress admin panel, click the Elementor button in the top left corner and then “Exit to WordPress”.

Elementor > Site Settings > Global Colors

Here, you can specify primary, secondary, text, and accent colors as well as additional optional colors.

Elementor > Site Settings > Global Fonts

Here, you can specify primary, secondary, text, and access font family and styles as well as additional custom fonts.

Elementor > Site Settings > Typography

Here, you can specify font family and style for body text and H1 – H5 headings.

Elementor > Site Settings > Button Style

Here, you can specify how your buttons should look.

Elementor > Site Settings > Image Style

Here, you can specify how your images should look.

Elementor > Site Settings > Form Fields

Here, you can edit the style of form fields.

Elementor > Site Settings > Header

Here, you can edit the header of the active theme (in this case, Hello Elementor). If you scroll down, you can optionally create a custom header using the Theme Builder.

You can also access the Theme Builder from the Elementor menu in the top left corner.

The Theme Builder allows you to create a template for various parts of your site (header, footer, single post, single page, etc).

For example, under Header, when you click to “Add New”, a popup will appear with premade header blocks to insert into your site.

The library has many types of content blocks to choose from.

For example, here are “About” blocks.

If you click the “Pages” tab in the Library, you’ll see a bunch of premade pages to insert into your website.

And if you click the “My Templates” tab, you’ll see any custom block or page templates you’ve created and saved. For example, once you’re done creating a page, you can click the down arrow in the top right corner and then click “Save as Template”.

Elementor > Site Settings > Footer

Like the header, here, you can edit the footer of the active theme (in this case, Hello Elementor). If you scroll down, you can optionally create a custom header using the Theme Builder.

Elementor > Site Settings > Site Identity

Here, you can specify a site name, description, logo, and favicon.

Elementor > Site Settings > Background

Here, you can set a background for your website. It could be a solid color, a gradient, a video, etc.

Elementor > Site Settings > Layout Settings

Here, you can specify the width of your websites content area, default container padding, the gap size between widgets, breakpoints, etc.

Elementor > Site Settings > Layout Settings

Here, you can enable image lightbox, which opens all image links in a popup window. You can edit the style of the lightbox here.

Elementor > Site Settings > Page Transitions

Here, you can specify how a page transitions from one to another, e.g. fade in, zoom in, slide, etc.

Elementor > Site Settings > Custom CSS

Here, you can enter any custom global CSS.

Elementor > Add Element > Widgets

To start building a page from scratch, you’ll click on the + icon to open the widget library. There are

  • layout widgets (flexbox and grid),
  • basic widgets (text, image, etc)
  • Pro widgets (loop grid, loop carousel, etc)
  • general widgets (tabs, accordion, etc)
  • link in bio widgets (minimalist, classic, etc)
  • site widgets (site logo, site title, etc)
  • single widgets (these are widgets for single posts, e.g. post title, post excerpt, etc)
  • WordPress widgets (pages, calendar, etc)

Add Widgets to a Page

To start building a page, drag a widget from the widget library to the location in the page where you want the widget to appear. Most of the time, you will start with a container layout widget. The default container is “Flexbox”, which is used for any type of layout. If you are created a symmetrical layout, you can choose “Grid”. Note that for each widget, there are 3 tabs with controls to configure various settings of the widget:

  • Layout
  • Style
  • Advanced

As you can see below. I’ve added a simple flexbox container to the page.

Now, let’s add some text on the left and an image on the right. Drag the text widget to the container. There is placeholder text.

Now, drag the image widget to the container. By default, we see a huge placeholder image and it appears below the text.

In the Content tab of the image widget settings, let’s replace the image with any test image.

Our page now looks like this.

Since we want the text and image to be in a row, we need to click on the container (6 dots)

and then click “Row” (right arrow) under “Items”.

The page now looks like this.

Tablet Preview

In the top center of the Elementor editor, you will see which page you are editing with a dropdown of other pages you can edit. There are also buttons for desktop, tablet, and mobile to preview how your site looks in each device. Clicking the tablet button shows us how the page looks on table.

Mobile Preview

Similarly, we can see a preview of the page on mobile.

Elementor > Structure

If you click the Structure button in the top left, a popup will appear showing the structure of the various elements on the page except for the header and footer. Since we added a container with nested text and image blocks, we see that structure in the Structure popup.

Elementor > Preview

To preview the page, click the eye icon next to the Publish button.

Insert a Block

Below the section we just added, let’s add a premade block. Click the folder icon to add a template.

Browse the premade blocks, choose one, hover over it, and click “Insert”.

You’ll see that the block has been added to our page.

Publish

Right now, our home page still looks like this.

Click the pink Publish button in the top right and then reload your site. Your changes will be live.

Insert a Page Template

Go back to the WordPress admin and click on another page to edit, e.g. the About page.

In the WordPress editor, click the “Edit with Elementor” button to edit the page in Elementor.

Now, click the folder icon to open the template library.

Click the “Pages” tab, browse the page templates, hover over one, and click “Insert” to insert it into our About page.

We now see that page template inserted into our About page. We can edit the content and then publish it.

Browsing the premade templates, inserting them into your pages, and inspecting how they are set up is one way to learn how to create pages from scratch

Last but not least, if you need any help, you can read the Elementor docs at https://elementor.com/help/elementor-editor/

Fast and Secure Marketing Website Tech Stack Options

Before I list some common options for marketing website options, it’s worth going over a few topics first.

Speed

No matter what type of website you are building, you want it to be a fast as possible. Speed is critically important for SEO and a good UX. To achieve the fastest speed, you need the following:

  • a static website (dynamic sites like WordPress that render PHP files with each request are slower)
  • hosting the website files on a content delivery network (CDN)
  • optimized and compressed images, preferably using a dedicated CDN like ImageKit or Cloudinary)
  • optimized and minified CSS, e.g. using Tailwind CSS to build only the CSS that is needed)
  • optimized and minified JavaScript
  • other techniques such as lazy-loading of images and infinite caching with unique (e.g. with a timestamp) asset filenames whenever a file changes

Security

Having a secure website is critical, especially for businesses. Some best practices for website security are

  • delivering a static website since dynamic ones, e.g. WordPress powered by PHP and numerous plugins by different authors are much easier to hack
  • multi-factor Authentication (MFA) (If you are using a CMS like WordPress, you can add 2FA to each user’s login. You can also restrict access to the admin panel to just a block of IP addresses for your company and requiring VPN access)
  • implementing a Zero Trust policy, where you assume everyone who has access is a potential threat and proceeding accordingly
  • applying a principle of least privilege (POLP) by limited user access to the minimum needed
  • requiring complex passwords
  • rotating passwords by forcing users to change their password regularly
  • keeping all dependencies up to date, including WordPress plugins
  • deleting using WordPress plugins rather than just deactivating them (yes, it’s possible to hack a WordPress site even via a deactivated plugin)
  • having regular backups
  • implement a Content Security Policy (CSP), which limits which resources can be loaded and from what domain
  • add SSL/TLS to transfer data encrypted (check SSL Labs and Hardenize for configuration recommendations)
  • use a code-scanning service to regularly check version control for sensitive data
  • rename default file / folder names, e.g. wp-admin for WordPress
  • disable unused features, e.g. WordPress’ REST API URLs

Content Management System

WordPress with or without a builder

WordPress is the #1 most popular CMS. If you use the default WYSIWYG editor (Gutenberg), you can create simple web pages, but if you want to more design and layout customization options without coding, you’ll need to use a website builder plugin like Elementor, Oxygen Builder, and WPBakery. With so many plugins, you can easily add website features like a hero carousel without coding by just installing a plugin and using it. However, it’s important to know WordPress’ limitations:

  • slower than a static site because the live site has to be dynamically rendered from PHP files first
  • less secure than a static site
  • development and customizations are somewhat limited or come with a learning curve as you have to work within the WordPress’ ecosystem
  • content is stored in a database, which is less user-friendly to work with for developers
  • version control uses WordPress’ custom versioning system rather than industry-standard git

Webflow

Webflow is a popular website builder that provides a WYSIWYG interface for creating very custom-looking websites. Its UI is powerful, but the many options may make it overwhelming for non-technical users. Websites are either hosting on Webflow’s servers or you can manually export a static version of the site each time you want it.

Headless CMS like Contentful

Contentful is a headless CMS. It allows you to create custom content models. Content is stored in Contentful and then fetched using the Contentful API. Note that you can also use WordPress in a headless manner via the REST API and WPGraphQL.

Content Contributors

Some marketing leaders will insist that many or all members of marketing, most of whom are non-technical, should be able to update the website. From my experience, most marketers don’t want to update the website themselves, even if they knew how to. They prefer to just open a ticket, like in Asana, or worse yet, send an email or a chat message asking for an update to be made. If non-technical

Having said that, from my experience, the one section of a website that is updated very frequently is the blog by many different authors. Unlike product pages, which are created once in a while, and tend to have fancy designs with animation, blog posts are very simple and they follow the same simple template. As such, it would make sense for non-technical people to be able to stage blog posts using a CMS.

Development Time

React

Even though React is the most popular JavaScript library for building user interfaces (UIs) with reusable components, that doesn’t mean it’s the best (I prefer Svelte) nor is it needed for all types of websites. I think most developers would agree with me that React is better suited for interactive web applications rather than primarily static marketing websites. Though you can create a static marketing website with React and a framework like Next.js, it makes no sense to because there will be a lot of unnecessary overhead that will slow down development and updates.

Dynamic functionality

You might argue that you need React for some dynamic functionality in your marketing website, like a page to filter resources. First of all, unlike SaaS web apps, where each company’s app have different requirements, most marketing websites have more or less the same functionality. For example, if you need a page to filter resources, you can use the Hushly Content Hub. Not only does it provide fully customizable filtering functionality and design, it allows non–technical marketers to log in and upload resources themselves, so web developers don’t have to waste time doing non-development work. Another common type of dynamic functionality is an ROI calculator. There are many services that provide this and which are easily customizable, e.g. grid.is.

Of course, there may be a time when you need a custom solution, but it doesn’t make sense to have your entire website depend on React or Svelte or some other JavaScript library just for a few edge cases. In this case, developers can use React and Svelte to create a frontend-only app.

Image Optimization

You really don’t need to waste time manually optimizing images anymore. Not only is that time-consuming, you can mostly get better results by using a dedicated image optimizer and CDN like ImageKit and Cloudinary.

Now that that’s out of the way, here are some options I’d recommend for a marketing website.

Marketing Website Tech Stack Options

Option 1

If non-technical people will only update certain parts of the website, e.g. press releases and blog posts, and if there are sufficient and skilled developer resources, then I recommend the following stack:

  • Eleventy or similar (for static site generation)
  • GitHub or similar (for version control – text files only)
  • Netlify or similar (for CI/CD and hosting)
  • Contentful or similar (a headless CMS for custom content models)
  • WordPress (used in a headless manner via the REST API or GraphQL for blog posts)
  • WPengine or similar (for WordPress hosting)
  • Tailwind CSS (for optimized CSS)
  • Tailwind UI or similar (component library – optional)

Option 2

If non-technical people will update most of the site and the site and/or there are limited developer resources, then I recommend the following stack:

  • WordPress (used in a headless manner)
  • WPengine or similar (for WordPress CMS hosting)
  • Simply Static (a WordPress static site generator plugin)
  • Github or similar (for storing static files and for version control)
  • Netlify or similar (for CI/CD and production website hosting)
  • ShortPixel (for image optimization)
  • Advanced Custom Fields / ACF (for if you need custom fields)
  • Elementor or similar (website builder – optional)

In both cases, I recommend the following as well.

  • Cloudflare (for at-cost domain registration and DNS management)
  • AWS S3 or similar (for hosting binaries – images, PDFs, etc)
  • AWS Cloudfront or similar (for delivering non-image binaries, e.g. PDFs)
  • ImageKit, Cloudinary or similar (for automatic image optimization)