Using Images Outside the public Folder in Next.js

 ・ 3 min

photo by Jon Tyson on Unsplash

When building a dev blog with Next.js, one of the most essential yet annoying tasks was handling images. Back when I used Gatsby, it handled everything automatically, which was great. I decided to switch to Next.js over Gatsby, but image handling in Next.js turned out to be surprisingly tricky.

First, you should know that I'm using Next.js as an SSG (Static Site Generator). This means all HTML is generated at build time and hosted on GitHub Pages. It's similar to just serving static files with NGINX. There's no server rendering involved in production.

I also write posts using Obsidian inside this project. There's an .obsidian config folder inside the blog folder. In Obsidian, I write posts in the project-root/blog folder. Posts go in blog/posts and images are saved in blog/assets.

There are 2 ways to display images in HTML:

  1. Providing the image file path
  2. Converting the image file to base64 and displaying it as a very long string

For remote images, you can either convert them to base64 during server rendering or just fetch them from a CDN path. I use Unsplash and Yes24 for remote image URLs.

The bigger problem for me was local images. When I attached images in Obsidian, they displayed fine there, but in the web environment (both development and production), the images appeared broken.

To solve the image problem, I referenced two blog posts:

  1. Use relative paths in markdown and MDX images with Next.js -> Image path conversion
  2. Contentlayer with next/image -> Converting images to base64

My first attempt to fix the image problem was to find all images during the markdown-to-HTML conversion process and replace the src paths with base64-encoded strings. This solved the problem easily, but as the number of images in a blog post grew, the website became slower.

Since images were slowing down the site, I needed to switch back to loading images from file paths. I added a custom function (remarkSourceRedirect) to remarkPlugins that modifies image src values so that Obsidian paths work in both development and production environments. And since Next.js only recognizes images from the public folder, I added predev and prebuild scripts to copy files from project-root/blog/assets to project-root/public/blog/assets.

For SSG, you must disable the image optimization option in next.config.js like this:

const isDev = process.env.NODE_ENV === 'development'
 
const nextConfig = {
  output: isDev ? 'standalone' : 'export',
  reactStrictMode: true,
  swcMinify: true,
  images: {
    unoptimized: true, // This right here!
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'image.yes24.com',
        pathname: '/**',
      },
      {
        protocol: 'https',
        hostname: 'images.unsplash.com',
      },
    ],
  },
}
 
module.exports = withContentlayer(nextConfig)

For a more improved approach, check out Reducing Next.js Build Size from 105MB to 16MB!


Make the best use of what is in your power and take the rest as it happens.

— Epictetus


Other posts
Things to Add to the Blog! 커버 이미지
 ・ 1 min

Things to Add to the Blog!