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');
}
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.
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)
ya totally! https://github.com/jay-manday/jm-site-react
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,
},
},
]
See https://github.com/zeit/next.js/issues/512#issuecomment-364782832.
https://github.com/rohanray/next-fonts ... it supports both local file loaders and url loaders
Most helpful comment
One question?
Cant we use urls like this?