Last updated: March 10, 2025

<span aria-hidden="true" id="free-image-cdn-github-pages--js"></span>

# Free image CDN: Github Pages & JS

![Tim Koschützki](/assets/images/teammates/avatar-tim-kos-1.jpg?dpl=dpl_C6YH6XrtnwbHLJcm1LDKywQn4CB3)

#### Tim Koschützki

Co-founder · Berlin, Germany · Show bio

[](https://x.com/tim%5Fkos)[](https://github.com/tim-kos)

Delivering optimized images quickly is crucial for web performance. While commercial CDNs offer robust solutions, developers can set up a free image CDN using GitHub Pages and JavaScript-based CDN libraries.

<span aria-hidden="true" id="understanding-image-cdns"></span>

## Understanding image CDNs

An Image CDN (Content Delivery Network) caches and serves images from servers geographically closer to users, significantly improving load times. Optimizing images through a CDN reduces bandwidth, enhances SEO, and boosts overall web performance.

<span aria-hidden="true" id="setting-up-github-pages"></span>

## Setting up Github Pages

GitHub Pages provides free static hosting directly from your GitHub repositories:

1. Create a GitHub repository named `image-cdn`.
2. Clone the repository locally:

```bash
git clone https://github.com/your-username/image-cdn.git  
cd image-cdn  
```

3. Create an `images` folder and add your images.
4. Commit and push your changes:

```bash
git add images  
git commit -m "Add initial images"  
git push origin main  
```

5. Enable GitHub Pages in your repository settings, selecting the `main` branch as the source.

Your images are now accessible via`https://your-username.github.io/image-cdn/images/your-image.jpg`.

<span aria-hidden="true" id="github-pages-limitations"></span>

### Github Pages limitations

Be aware of GitHub Pages' limitations:

* Soft bandwidth limit of 100GB per month.
* Recommended repository size under 1GB (strongly recommended under 5GB).
* No built-in image processing.
* The default GitHub Pages domain may be blocked in some corporate environments.

These limitations make GitHub Pages suitable for small to medium projects.

<span aria-hidden="true" id="integrating-a-javascript-cdn"></span>

## Integrating a Javascript CDN

To optimize image delivery, integrate a JavaScript CDN library like[jsDelivr⁠](https://www.jsdelivr.com/). jsDelivr provides global caching.

Reference your images through jsDelivr:

```html
<img
  src="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/your-image.jpg"
  alt="Optimized Image"
/>

```

This ensures your images are cached globally.

<span aria-hidden="true" id="reliability-and-fallback"></span>

### Reliability and fallback

While jsDelivr offers excellent performance, implement fallback strategies:

```javascript
function loadImage(imageElement, primarySrc, fallbackSrc) {
  imageElement.onerror = function () {
    console.warn('Primary CDN failed, using fallback')
    imageElement.src = fallbackSrc
  }
  imageElement.src = primarySrc
}

const img = document.getElementById('my-image')
loadImage(
  img,
  'https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image.jpg',
  'https://your-username.github.io/image-cdn/images/image.jpg',
)

```

Alternative CDN options include Statically, unpkg, or Cloudflare Pages.

<span aria-hidden="true" id="automating-with-github-actions"></span>

## Automating with Github Actions

Automate image optimization and deployment using GitHub Actions:

Create `.github/workflows/compress-images.yml`:

```yaml
name: Compress Images
on:
  pull_request:
    paths:
      - '**.jpg'
      - '**.jpeg'
      - '**.png'
      - '**.webp'
jobs:
  build:
    if: github.event.pull_request.head.repo.full_name == github.repository
    name: calibreapp/image-actions
    permissions:
      contents: write
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4
      - name: Compress Images
        uses: calibreapp/image-actions@main
        with:
          githubToken: ${{ secrets.GITHUB_TOKEN }}

```

This workflow automatically optimizes images on pull requests.

<span aria-hidden="true" id="implementing-custom-optimization"></span>

## Implementing custom optimization

For more control, use a Node.js script with Sharp:

```javascript
const sharp = require('sharp')
const fs = require('fs').promises
const path = require('path')

async function optimizeImage(inputPath, outputPath, options = {}) {
  try {
    const image = sharp(inputPath)
    const metadata = await image.metadata()

    const config = {
      jpeg: { quality: 80, progressive: true },
      png: { quality: 80, compressionLevel: 9 },
      webp: { quality: 80 },
    }

    switch (metadata.format) {
      case 'jpeg':
        await image.jpeg(config.jpeg).toFile(outputPath)
        break
      case 'png':
        await image.png(config.png).toFile(outputPath)
        break
      case 'webp':
        await image.webp(config.webp).toFile(outputPath)
        break
      default:
        throw new Error(`Unsupported format: ${metadata.format}`)
    }

    console.log(`Optimized: ${path.basename(inputPath)}`)
    return outputPath
  } catch (error) {
    console.error(`Failed to optimize ${inputPath}:`, error)
    throw error
  }
}

async function processDirectory(inputDir, outputDir) {
  try {
    await fs.mkdir(outputDir, { recursive: true })
    const files = await fs.readdir(inputDir)

    for (const file of files) {
      const inputPath = path.join(inputDir, file)
      const outputPath = path.join(outputDir, file)
      const stat = await fs.stat(inputPath)

      if (stat.isFile() && /\.(jpe?g|png|webp)$/i.test(file)) {
        await optimizeImage(inputPath, outputPath)
      }
    }

    console.log('All images processed successfully')
  } catch (error) {
    console.error('Directory processing failed:', error)
  }
}

processDirectory('original-images', 'optimized-images')

```

<span aria-hidden="true" id="testing-your-setup"></span>

## Testing your setup

Verify your CDN:

1. **Browser Developer Tools**: Check the Network panel.
2. **Performance Testing Tools**:
   * [GTmetrix⁠](https://gtmetrix.com/)
   * [WebPageTest⁠](https://www.webpagetest.org/)
   * [Lighthouse⁠](https://developers.google.com/web/tools/lighthouse)
3. **Real User Monitoring**: Use the Performance API:

```javascript
document.addEventListener('DOMContentLoaded', () => {  
  window.addEventListener('load', () => {  
    const imageEntries = performance  
      .getEntriesByType('resource')  
      .filter((entry) => entry.initiatorType === 'img')  
    const totalLoadTime = imageEntries.reduce((sum, entry) => sum + entry.duration, 0)  
    const avgLoadTime = totalLoadTime / imageEntries.length  
    console.log(`Average image load time: ${avgLoadTime.toFixed(2)}ms`)  
    imageEntries.forEach((entry) => {  
      console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`)  
    })  
  })  
})  
```

<span aria-hidden="true" id="security"></span>

## Security

Implement these security measures:

<span aria-hidden="true" id="cors-headers"></span>

### CORS headers

Create a `_headers` file:

```http
Access-Control-Allow-Origin: *
Cache-Control: public, max-age=31536000
Content-Security-Policy: default-src 'self'; img-src 'self' https://cdn.jsdelivr.net

```

<span aria-hidden="true" id="prevent-hotlinking"></span>

### Prevent hotlinking

If traffic exceeds limits, use a JavaScript check:

```javascript
document.addEventListener('DOMContentLoaded', () => {
  const images = document.querySelectorAll('img[data-src]')
  const allowedDomains = ['yourdomain.com', 'localhost']

  if (allowedDomains.some((domain) => window.location.hostname.includes(domain))) {
    images.forEach((img) => {
      img.src = img.getAttribute('data-src')
    })
  }
})

```

<span aria-hidden="true" id="advanced-optimization"></span>

## Advanced optimization

<span aria-hidden="true" id="responsive-images"></span>

### Responsive images

```html
<img
  srcset="
    https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image-small.jpg   480w,
    https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image-medium.jpg  800w,
    https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image-large.jpg  1200w
  "
  sizes="(max-width: 600px) 480px,
         (max-width: 1200px) 800px,
         1200px"
  src="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image-large.jpg"
  alt="Responsive Image"
  loading="lazy"
/>

```

<span aria-hidden="true" id="modern-image-formats"></span>

### Modern image formats

Use a format detection and fallback system:

```html
<picture>
  <source
    srcset="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image.avif"
    type="image/avif"
  />
  <source
    srcset="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image.webp"
    type="image/webp"
  />
  <img
    src="https://cdn.jsdelivr.net/gh/your-username/image-cdn/images/image.jpg"
    alt="Optimized image with format fallbacks"
    loading="lazy"
  />
</picture>

```

<span aria-hidden="true" id="benefits-and-limitations"></span>

## Benefits and limitations

<span aria-hidden="true" id="benefits"></span>

### Benefits:

* Zero cost for small to medium projects.
* Easy setup and automation.
* Improved global performance.

<span aria-hidden="true" id="limitations"></span>

### Limitations:

* GitHub Pages has bandwidth (100GB/month) and storage limits (1-5GB recommended).
* Less control compared to commercial CDNs.
* No built-in image transformation.

For larger projects or advanced needs, consider services like[Transloadit's Image Processing API](/services/image-processing.md).

By following these steps, you've created a free image CDN using GitHub Pages, a JavaScript CDN, and GitHub Actions.

\#image-cdn-free#github-pages#javascript-cdn#image-optimization#web-performance#content-delivery-service

### 👩‍💻 Join 20k+ developers

Sign up for our [monthly newsletter](/newsletters.md) to receive direct links to 3 exclusive tech — and 2 product updates. No less, no more.

Your email:

Get access

## File uploading and encoding. Made simple.

Transloadit streamlines file handling for developers, trusted by brands like Coursera and The New York Times. We’re known for a reliable API, top-notch support, and a strong commitment to open source, with projects like [Uppy⁠](https://uppy.io) and [Tus⁠](https://tus.io) setting standards in file processing.

[Sign up](/c/)[Book a Demo](https://survey.typeform.com/to/kRg47Xi5)

No credit card needed · 5 GB included in the free plan

Cancel anytime
