Next.js: Unable to use self-hosted fonts using NextJS

Created on 25 Jul 2017  路  19Comments  路  Source: vercel/next.js

I am compiling SCSS and externalizing the resulting CSS into a file using sass-loader and extract-text-webpack-plugin in next.config.js. The compiled CSS file is being emitted into the .next folder. I also have some static resources, i.e. images and fonts that I'm referencing in the stylesheet. These static resources are also being compiled into the .next folder using the file-loader plugin in next.config.js.

However, when the page loads in the browser, I get a 404 on these resources because NextJS seems to prepend their paths with a /_next/webpack/ in the compiled CSS file:

http://54.197.22.181/_next/webpack/52a802f51895f840821c1083434f3d84.ttf

Is there any workaround to this issue? I really need to use SASS and keep the compiled CSS as an external file as against embedded style. So, the CSS-in-JS model isn't what I'm comfortable with.

For reference, my project is hosted at https://github.com/amitsch666/nano.

Here's the relevant rule I have in my next.config.js:

{ test: /\.(ttf|eot|png)$/, loader: 'file-loader' }

Here's the font declaration in my scss file:

@font-face {
    font-family: 'Lemonada';
    src: url('../static/fonts/Lemonada/Lemonada-Regular.ttf');
    src: url('../static/fonts/Lemonada/Lemonada-Bold.ttf');
    src: url('../static/fonts/Lemonada/Lemonada-SemiBold.ttf');
    src: url('../static/fonts/Lemonada/Lemonada-Light.ttf');
}
  • [x] I have searched the issues of this repository and believe that this is not a duplicate.

Update: I have managed to make my site work by routing all _next/webpack/static/ requests to /.next/static in my Express middleware. However, this still seems a tad hackish to me and would love to hear if someone has a more "standard" solution.

Most helpful comment

One question?

Cant we use urls like this?

@font-face {
    font-family: 'Lemonada';
    src: url('/static/fonts/Lemonada/Lemonada-Regular.ttf');
    src: url('/static/fonts/Lemonada/Lemonada-Bold.ttf');
    src: url('/static/fonts/Lemonada/Lemonada-SemiBold.ttf');
    src: url('/static/fonts/Lemonada/Lemonada-Light.ttf');
}

All 19 comments

Have also been struggling with the same problem, unfortunately @amitsch666's nice solution to use express to serve the font's as a static folder has not worked. I tried a few different css oriented things so it's definitely a webpack issue. Really interested in seeing this feature patched.

Thanks!

Try to inline your fonts into webpack module by using https://github.com/quadric/babel-plugin-inline-import or https://github.com/elliottsj/babel-inline-import-loader

This would increase the app.js bundle size but maybe that's the only way out

One question?

Cant we use urls like this?

@font-face {
    font-family: 'Lemonada';
    src: url('/static/fonts/Lemonada/Lemonada-Regular.ttf');
    src: url('/static/fonts/Lemonada/Lemonada-Bold.ttf');
    src: url('/static/fonts/Lemonada/Lemonada-SemiBold.ttf');
    src: url('/static/fonts/Lemonada/Lemonada-Light.ttf');
}

@arunoda, I thought so too but the compiled css and folt files are being emitted into the .next/ folder and not static/. There's no way I could make Webpack emit the compiled resources into static/. Here's what my next.config.js looks like:

const path = require('path')
const glob = require('glob')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

module.exports = {
    distDir: '.build',
  webpack: (config) => {
    config.module.rules.push(
      {
        test: /\.(css|scss)$/,
        loader: 'emit-file-loader',
        options: {
                    name: path.join('dist', '[path][name].[ext]')
        }
      },
      {
        test: /\.(css|sass|scss)$/,
        use: ExtractTextPlugin.extract([
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: false,
              minimize: true
                        }
                    },
                    'postcss-loader',
          {
                        loader: 'sass-loader',
            options: {
              includePaths: ['styles', 'node_modules']
                .map((d) => path.join(__dirname, d))
                .map((g) => glob.sync(g))
                .reduce((a, c) => a.concat(c), [])
            }
          },
        ])
      },
            {
                test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
                loader: 'url-loader?limit=10000&mimetype=application/font-woff&outputPath=static/'
            },
            {
                test: /\.(svg|ttf|eot)$/i,
                loader: 'file-loader?outputPath=static/'
            },
            {
                test: /\.(png|jpe?g|gif)$/i,
                loaders: [
                    'file-loader?outputPath=static/',
                    'image-webpack-loader?bypassOnDebug&optimizationLevel=7&interlaced=false'
                ]
            },
    )
        config.plugins.push(
            new ExtractTextPlugin({
                filename: path.join('static', 'main.css')
            }),
        )
    return config
  }
}

@amitsch666

Then use our custom server. (May be with express)
Then create route for fonts which serves file from the .next directory.

That's exactly what I'm doing right now. Was just wondering if there were a more standard way of accessing custom fonts from external stylesheets (as against embedded style/jsx) in the NextJS environment.

@arunoda, Hi, can you help, Im trying to get this working too, but Im getting error

Cannot find module './logo.png'
Error: Cannot find module './logo.png'
    at Function.Module._resolveFilename (module.js:485:15)
    at Function.Module._load (module.js:437:25)
    at Module.require (module.js:513:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/home/bjorn/projects/ran/.next/dist/app/components/Logo/index.js:22:13)
    at Module._compile (module.js:569:30)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:503:32)
    at tryModuleLoad (module.js:466:12)
    at Function.Module._load (module.js:458:3)

I have logo.png inside my Logo component

import { Link } from '~/routes'
import styled from 'styled-components'
import logoSrc from './logo.png'
console.log(logoSrc) # getting /_next/webpack/static/9e594b9314759b61950cb816873318ea.png here

export const Img = styled.img`
   ...
`
const Logo = () =>
  <Link href="/">
    <Img src={logoSrc} alt="logo" />
  </Link>

export default Logo

and If I go to http://localhost:3000/_next/webpack/static/9e594b9314759b61950cb816873318ea.png I can see image

My next.config.js

    // Images task
    config.module.rules.push({
      test: /\.(png|jpe?g|gif)$/i,
      loaders: [
        'file-loader?outputPath=static/', 
      ]
    })

@BjornMelgaard I had the same problem, a work around this is to just put your png's and other assets in a static directory.

In server.js put server.use('/static', express.static('/next')) to create a static route to assets, this should circumvent the issue.

@jay-manday , are you saying that you put your images in project_root/static directory? This directory is served automatically

In my example file-loader?outputPath=static/ output images like project_root/.next/static/hash.ext, so url http://localhost:3000/_next/webpack/static/9e594b9314759b61950cb816873318ea.png is right for this import and I can see image if I go in browser by this url

I've tried your suggestion and it didnt help

yes you'd put your images, font files, etc in the folder./static. The snippet i posted before will have express serve a static route that doesn't hit webpack from my understanding. You can then import the images and fonts directly from their like any other file.

@jay-manday can you provide example, please)

Ok, I have found proper configuration, and Its fucking weird, because next don't support custom webpack loaders, explanations in comments

    // Image task to use images in component directory
    config.module.rules.push({
      test: /\.(png|jpe?g|gif)$/i,
      use: [
        // using emit-file-loader just to shut up 'Cannot find module',
        // it will make copy of image in component directory
        {
          loader: 'emit-file-loader',
          options: {
            name: 'dist/[path][name].[ext]'
          }
        },
        // this will create image copy, that we will use,
        // output path - '/.next/static/longhash.png'
        // url - '/_next/static/longhash.png'
        {
          loader: 'url-loader',
          options: {
            outputPath: 'static/',
            publicPath: '/_next/',
            limit: 1000
          }
        },
        {
          loader: 'image-webpack-loader',
          options: {
            gifsicle: {
              interlaced: false
            },
            optipng: {
              optimizationLevel: 7
            },
            pngquant: {
              quality: '65-90',
              speed: 4
            },
            mozjpeg: {
              progressive: true,
              quality: 65
            }
          }
        }
      ]
    })

server/index.js

const express = require('express')
const next = require('next')
const path = require('path')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare()
.then(() => {
  const server = express()
  const staticDir = path.resolve(__dirname, '..', '.next/static')
  server.use('/_next/static', express.static(staticDir))

  server.get('*', (req, res) => {
    return handle(req, res)
  })

  server.listen(port, (err) => {
    if (err) throw err
    console.log(`> Ready on http://localhost:${port}`)
  })
})

working example you can find here

@BjornMelgaard this actually didn't work for me. It outputs /_next/webpack/static/9705a64cd8f6a22ed81cdb55ca19a78d.png in a production build. Local is fine though. What do you end of with in production?

@jonkwheeler, fk, @jay-manday was right that images must be distributed bypassing webpack (seems like _next/webpack/static/9705a64cd8f6a22ed81cdb55ca19a78d.png was distributed by webpack-dev-middleware, not sure, im not expert), I'have updated comment with my example

I would recommend for https://github.com/zeit/next.js/blob/master/examples/with-global-stylesheet/README.md and use postcss-url plugin with inline option, so we don't need to care about output path problem.
For fonts, I use custom server route for the css and font files, so I could change to use CDN later easily.

@BjornMelgaard Great solution!

If anyone needs to get SVGs working without converting them to base64 via url-loader, I suggest using https://github.com/bhovhannes/svg-url-loader. It can be used with same options as url-loader but without base64 conversion of SVG images.

For example:

test: /\.svg$/i,
use: [
  {
    loader: 'emit-file-loader',
    options: {
      name: 'dist/[path][name].[ext]',
    },
  },
  {
    loader: 'svg-url-loader',
    options: {
      outputPath: 'static/',
      publicPath: '/_next/',
      limit: 1000,
    },
  },
]

https://github.com/rohanray/next-fonts ... it supports both local file loaders and url loaders

Was this page helpful?
0 / 5 - 0 ratings