Using JAMSTACK in 2025 for SASS
JAMstack, which stands for JavaScript, APIs, and Markup, is a modern web development architecture that offers several advantages over traditional methods. By decoupling the frontend from the backend, JAMstack allows developers to build fast, secure, and scalable websites and applications. The architecture leverages static site generators and serverless functions, which can significantly improve performance by serving pre-rendered pages and reducing server load. Additionally, JAMstack enhances security by minimizing the attack surface, as there is no direct connection to a database or server. This approach also facilitates easier scaling and maintenance, as developers can focus on individual components without affecting the entire system.
When using JAMstack, it is recommended you use it for the content part of the website such as information about your services, blog articles, content pages, landing pages, etc. We will also add a light serverless function to power a contact us form if you need one. Your site can be www.yourdomain.com and actual app can then be hosted on app.yourdomain.com or something similar where users can be greeted by a login page and you can use what ever stack you like.
Let's get started with using nodejs and eleventy for your static site generation.
Install node modules
Install 11ty and it's image component as your easy static site generator.
npm init -y
npm pkg set type="module"
npm install @11ty/eleventy
npm install @11ty/eleventy-img
Add eleventy.config.js. This set up the image module and builds your site from the src folder to the public folder.
import { eleventyImageTransformPlugin } from "@11ty/eleventy-img";
export const config = {
dir: {
input: "src",
output: "public",
},
};
export default function (eleventyConfig) {
eleventyConfig.addPassthroughCopy("src/robots.txt");
eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
formats: ["webp", "jpeg"],
widths: ["auto"],
htmlOptions: {
imgAttributes: {
loading: "lazy",
decoding: "async",
},
pictureAttributes: {},
},
});
}
Tailwind setup
Install tailwind for styling your content and templates.
npm install tailwindcss @tailwindcss/cli
Crease src/css folder for the tailwind css file you want to use for your theme. You can then create the final css file like this.
npx @tailwindcss/cli -i ./src/style.css -o ./public/css/style.css --watch
Create folders and config files
Your folders may look something like this.
.dev.vars (.env equivalent for wrangler)
functions
your-function.ts
tsconfig.json
types.d.ts
src
_data (for data files like the nav.json)
_includes (for your templates)
img (for global images)
blog (for your md files and other assets)
index.njk (your home page)
archive.njk (for searching your blog articles)
sitemap.njk (for creating a site map)
robots.txt
style.css
.gitignore
README.md
eleventy.config.js
package.json
wrangler.toml
Add .gitignore to exclude the public folder and node_modules folders.
public
node_modules
.DS_Store
Hosting on Cloudflare
For hosting and if you need to power a contact us form or something similar. Use Cloudflares pages and page functions. To use functions, create a functions folder and you can place your call handlers there.
npm install wrangler
npx wrangler pages download config project-name
npx wrangler types --path='./functions/types.d.ts'
I had to adjust the tsconfig.json file a bit to get the node modules to work correctly with type script.
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"lib": ["esnext"],
"types": ["./types.d.ts"],
"moduleResolution": "node"
}
}
I was having a real hard time finding typescript examples of a simple contact form handler so I will share the script here. This is specifically for just post calls and works with JSON data in and out.
import { EventContext } from "@cloudflare/workers-types";
import { Resend } from "resend";
export interface IFormData {
email: string;
message: string;
name: string;
}
export async function onRequestPost(context: EventContext<any, any, any>) {
const resend = new Resend(context.env.RESEND_API_KEY);
const { request } = context;
const formData: IFormData = await request.json();
const { data, error } = await resend.emails.send({
from: "Contact Form <[email protected]>",
replyTo: formData.email,
to: "[email protected]",
subject: "Contact Form Submission",
html: `<p>${formData.name} wrote the following message:</p><p>${formData.message}</p>`,
});
if (error) {
return new Response("", {
status: 500,
statusText: `Internal Server Error: ${error.message}`,
});
}
return Response.json({ data, error });
}
Running the project
To run the project, your package.json file may have something ike this in it. Build will create the static files and the css. Dev and dev-cf will bring up the website in developer mode so you have nice debug tools available to test all parts.
"scripts": {
"build": "npx @11ty/eleventy && npx @tailwindcss/cli -i ./src/style.css -o ./public/css/style.css --minify",
"dev": "eleventy --serve",
"cf-dev": "npx wrangler pages dev ./public --port 8080",
"css": "npx @tailwindcss/cli -i ./src/style.css -o ./public/css/style.css --watch"
},
Now you will have a lightning fast, super robust and secure site that costs nothing or very little to host and can handle almost any traffic you can throw at it.