Santanu
Santanu

Technology

Engine Room

Tech Notes about this web site

A description of cover image.

Engine Room: Building a Modern, Secure, and Fast Website

In this post, I’ll walk you through the thought process and technical steps behind creating this website, with a focus on performance, security, and simplicity. The journey has been filled with learning, experimentation, and a commitment to stay on the cutting edge of web development.

Project Goals

The main objectives were simple but ambitious:

  • Responsive Design: Prioritize mobile-first, ensuring the site looks and functions well across devices.
  • Ease of Management: Make it simple to update and maintain.
  • Top Performance: Achieve high scores on Google PageSpeed, aiming for the high 90s.
  • Security First: Maintain an “A” rating on Security Headers.
  • Code Ownership: Only include code we fully understand—no blind copy-pasting from tutorials or other sites.
  • Bleeding-Edge Updates: Stay on the latest versions, breaking things occasionally but learning a lot in the process. Learn more about this mindset with Reverse Engineering.
  • Frequent GitHub Commits: Maintain a robust GitHub repository with frequent commits for easy reversals if things go wrong.

The Approach

We built the site using modern tools like Astro and Tailwind CSS, so some of the specifics are tailored to these technologies. Here are the key decisions:

  • Package Manager: Using only pnpm for package management. ✅
  • TypeScript Configuration: tsconfig.json with alias @. ✅
  • Dark and Light Mode Toggle: Easy theme switching. ✅
  • Custom 404 Error Handling: Because nobody likes a boring error page. ✅
  • Smooth Page Transitions: Implemented Astro’s ViewTransitions for a seamless browsing experience.
  • Highlighted Active Menu Item: Keep track of where you are on the site. ✅
  • Working Search Feature: Integrated with Pagefind. 🔍
  • RSS Feed & Sitemap: Automatically generated for better SEO. ✅
  • Last Modified Date: Follows the Astro Documentation. ✅
  • Typography: Different fonts for headers, body text, and code. 📖
  • Build Optimization: Ensure all assets are compressed and minified before deployment. ✅
  • External Links: Open in a new tab, with a visual cue. ✅
  • Layout: Containerized content for better control, leaving the header and footer outside for flexibility. ✅
  • Footer: Almost complete; the coffee icon needs a little tweak. ☕ ✅

Getting Started

Here’s how to get the base project up and running with pnpm:

Terminal window
pnpm create astro@latest v2-tailwind-astro
# Choose empty project, strict mode
pnpm astro add tailwind
pnpm astro add mdx
pnpm astro add sitemap
pnpm add --save-dev --save-exact prettier prettier-plugin-tailwindcss
pnpm install -D @tailwindcss/typography

Additional commands to fine-tune the setup:

  • Sharp: With a strict package manager like pnpm, you may need to manually install Sharp, even though it’s an Astro dependency.
  • Day.js: For adding modified timestamps to markdown.
Terminal window
pnpm install sharp
pnpm add dayjs

Handling Astro Check Failures

I ran into some build issues due to poor JavaScript/TypeScript scripting, but after some Googling and forum-hopping, most of them were resolved fairly easily. It’s a good reminder that debugging is just part of the game!


Dark Mode: Current and Future Plans

Right now, the website uses Tailwind’s class-based dark mode with darkMode: 'class'. In future updates, I plan to move to a media-query-based strategy once Tailwind v4 is out of alpha. Here’s a quick look at the configuration and how the theme toggle works:

const theme = (() => {
if (localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
})();
document.documentElement.classList.toggle('dark', theme === 'dark');

The button controlling this looks something like:

<button id="themeToggle">SVG Icon Here</button>

RSS Feed

We integrated RSS to make following the blog easier. After installing the Astro RSS package, you can define the feed and even style it to make it more human-readable. Here’s the RSS generation script:

import rss from '@astrojs/rss';
export async function GET(context) {
const blog = await getCollection('blog');
return rss({
stylesheet: "/pretty-feed-v3.xsl",
title: 'Aashiyana of Pamela Santanu',
items: blog.reverse().map(post => ({
title: post.data.title,
description: post.data.description,
pubDate: post.data.pubDate,
link: `/blog/${post.slug}/`
})),
customData: `<language>en-us</language>`,
});
}

Sitemap

For SEO purposes, we implemented a sitemap using the pnpm astro add sitemap command. This ensures that search engines can efficiently crawl the site. Don’t forget to add the link to your <head>:

<link rel="sitemap" href="/sitemap-index.xml">

Here’s how you can integrate the security headers code into your blog, with a brief explanation of each header:


Adding Security Headers

To further enhance the security of the site, it’s crucial to configure HTTP security headers properly. SecurityHeaders is a good place to check your http header, and this gave me A Here’s the configuration we’re using:

"headers": [
{
"source": "**",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "X-Frame-Options", "value": "deny" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-XSS-Protection", "value": "1; mode=block" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.youtube.com; style-src 'self' 'unsafe-inline'; img-src 'self' ik.imagekit.io; frame-src https://www.youtube.com https://youtube-nocookie.com *.google.com"
}
]
}
]

Explanation of Each Header

  1. Access-Control-Allow-Origin: ”*“
    This allows any domain to access the site’s resources, enabling Cross-Origin Resource Sharing (CORS). You might adjust this based on your project needs to limit access.

  2. X-Frame-Options: “deny”
    Prevents the site from being embedded in an iframe, protecting against clickjacking attacks.

  3. X-Content-Type-Options: “nosniff”
    Ensures that browsers don’t try to guess the content type of files, reducing the risk of security vulnerabilities from MIME-type confusion.

  4. X-XSS-Protection: “1; mode=block”
    This enables basic cross-site scripting (XSS) protection in browsers, blocking pages that are detected to have XSS vulnerabilities.

  5. Referrer-Policy: “strict-origin-when-cross-origin”
    Controls how much information about the referring page is sent in the HTTP request. This setting limits the exposure of sensitive data when navigating across different domains.

  6. Content-Security-Policy (CSP)
    This is one of the most important headers for preventing various attacks, including XSS and data injection. Here’s how it’s configured:

    • default-src 'self': Limits all resources to the same origin.
    • script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.youtube.com: Allows scripts from the same origin, with YouTube as an exception.
    • style-src 'self' 'unsafe-inline': Allows inline styles, which Tailwind CSS needs, but restricts external styles to the same origin.
    • img-src 'self' ik.imagekit.io: Images are restricted to the same origin and the image hosting service ImageKit.
    • frame-src https://www.youtube.com https://youtube-nocookie.com *.google.com: Limits iframes to YouTube and Google sources.

This set of headers strengthens the overall security posture of the site while allowing necessary external resources like YouTube videos and images from ImageKit.

Search Functionality

Since Astro builds static websites without any server-side code, we needed a client-side search solution. Pagefind UI turned out to be a great option. Setting up JavaScript for Astro wasn’t straightforward, but after some research, it worked beautifully.

One downside: Pagefind requires looser CSP settings ('unsafe-eval', 'unsafe-inline'), which isn’t ideal for security, but that’s something we’re working on.


Active Menu Highlight

Making the navigation highlight the current page was surprisingly simple, but I’m still refining it. Here’s a snippet that handles this:

<a href={href} class:list={[
'link',
{ active: Astro.url.pathname === href || Astro.url.pathname.startsWith(href) }
]}>
{label}
</a>
.link.active {
color: #ec4899;
}

This project is a constant work-in-progress, with improvements rolling out steadily. From responsive design and smooth transitions to optimizing for speed and security, every step has been a learning experience. Stay tuned for more updates as we continue refining and tweaking!

Happy coding! 😁

Read Next

17 Apr   2024

The lost jihad

Written for on going conflict between Israel and Palestinian groups

9 Apr   2024

Chasing Miles in Kalimpong

Running Through Rain - my experience