diff --git a/Makefile b/Makefile
index 6dfbcc7..97b7f80 100644
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,6 @@ publish:
 	@if [ "$(local)" = "True" ]; then \
 		echo "Running npx quartz build --serve"; \
 		npx quartz build --serve; \
-		@./scripts/compress_images.sh; \
 	else \
 		echo "Restarting Docker Compose"; \
 		docker compose restart; \
diff --git a/quartz/build.ts b/quartz/build.ts
index 5752caa..88447d2 100644
--- a/quartz/build.ts
+++ b/quartz/build.ts
@@ -17,6 +17,28 @@ import { glob, toPosixPath } from "./util/glob"
 import { trace } from "./util/trace"
 import { options } from "./util/sourcemap"
 import { Mutex } from "async-mutex"
+import sharp from "sharp"
+
+async function compressImages(inputDir, outputDir) {
+  const files = await glob(`**/*.{jpg,png}`, inputDir);
+
+  console.log(`Found ${files.length} files:`);
+
+  // Use sharp for image compression
+  await Promise.all(
+    files.map(async (file) => {
+      const inputFilePath = path.join(inputDir, file);
+      const relativeOutputPath = path.relative(inputDir, inputFilePath);
+      const outputWebpPath = path.join(outputDir, relativeOutputPath.replace(/\.(jpg|png)$/, '.webp'));
+
+      await sharp(inputFilePath)
+        .webp({ quality: 75 })
+        .toFile(outputWebpPath);
+    })
+  );
+
+  console.log(`Compressed ${files.length} images.`);
+}
 
 async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
   const ctx: BuildCtx = {
@@ -56,6 +78,8 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
   const parsedFiles = await parseMarkdown(ctx, filePaths)
   const filteredContent = filterContent(ctx, parsedFiles)
   await emitContent(ctx, filteredContent)
+  await compressImages(argv.directory, argv.output);
+
   console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`))
   release()
 
@@ -147,6 +171,7 @@ async function startServing(
       // instead of just deleting everything
       await rimraf(argv.output)
       await emitContent(ctx, filteredContent)
+      await compressImages(argv.directory, argv.output);
       console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`))
     } catch {
       console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`))
diff --git a/scripts/compress_images.sh b/scripts/compress_images.sh
deleted file mode 100755
index 46d7a34..0000000
--- a/scripts/compress_images.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-
-# Set the source and destination directories
-SOURCE_DIR="content/images"
-DESTINATION_DIR="public/images"
-
-# Create the destination directory if it doesn't exist
-mkdir -p "$DESTINATION_DIR"
-
-# Iterate over image files in the source directory
-for IMAGE_PATH in "$SOURCE_DIR"/*.jpg "$SOURCE_DIR"/*.png; do
-  # Check if there are matching files
-  if [ -e "$IMAGE_PATH" ]; then
-    # Extract the filename and extension
-    FILENAME=$(basename -- "$IMAGE_PATH")
-    EXTENSION="${FILENAME##*.}"
-    FILENAME_NOEXT="${FILENAME%.*}"
-
-    # Compress the image using `sharp` (ensure sharp is installed: npm install sharp)
-    npx sharp "$IMAGE_PATH" --webp --quality=75 > "$DESTINATION_DIR/$FILENAME_NOEXT.webp"
-
-    echo "Compressed $IMAGE_PATH to $DESTINATION_DIR/$FILENAME_NOEXT.webp"
-  fi
-done
-
-echo "Image compression complete!"
\ No newline at end of file