Error while trying to add external local fonts in nextJS - next.js

I'm trying to add local font to my NextJS + TailwindCSS project.
I have added the font inside public/fonts folder and I'm following the docs:
This is my code
import localFont from '#next/font/local'
const inter = Inter({
subsets: ['latin'],
})
const recoleta = localFont({
src: 'fonts/Recoleta-Regular.ttf',
fontFamily: 'Recoleta',
})
And I'm getting this error from the terminal.
I need help on which folder to add it or how to configure it perfectly.
Module not found: Can't resolve './fonts/Recoleta-Regular.ttf'

Had this error and fixed the issue by setting it up as such. Used https://nextjs.org/docs/api-reference/next/font#src for assistance.
Using app folder.
page.tsx:
import CustomFont from '#next/font/local'
const cfont = CustomFont({
src: '../public/fonts/cfont.ttf',
variable: '--font-cfont',
})
export default function Home() {
return (
<div className={`${cfont.variable}`}>
<div className="font-cfont">
Test
</div>
</div>
)
}
tailwind.config.js:
const { fontFamily } = require('tailwindcss/defaultTheme')
/** #type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
fontFamily: {
cfont: ['var(--font-cfont)', ...fontFamily.sans],
},
},
},
plugins: [],
}

Related

How to add custom local fonts to a Nextjs 13 Tailwind project?

I have downloaded a couple fonts (NOT GOOGLE FONTS) and I want to add and use them in my Nextjs 13 Tailwind project.
I've followed the Nextjs docs to try add a single font (I want to add multiple fonts but trying to get a single font added isn't working):
npm install #next/font
Add the downloaded font files to /pages/fonts
Add the fonts to /pages/_app.js
Add the fonts to tailwind.config.js
Use font in a component
Updated /pages/_app.js
import localFont from '#next/font/local'
const surt = localFont({
src: './fonts/Surt-Normal-Bold.woff2',
variable: '--font-surt-bold',
})
export default function MyApp({ Component, pageProps }) {
return (
<main className={surt.variable}>
<Component {...pageProps} />
</main>
)
}
Updated tailwind.config.js (Surt is a sans-serif font)
const { fontFamily } = require('tailwindcss/defaultTheme')
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-surt)', ...fontFamily.sans],
},
},
},
plugins: [],
}
Updated About page
export default function About() {
return <div className="font-surt-bold">About</div>
}
What am I doing wrong and how would I update the code to add another font (eg Surt-Normal-Regular.woff2, Surt-Normal-Semibold-Italic.woff2)
After setting up TailwindCSS the way you did to use the font you should also add font-sans in the className you want to add the font to.
In this case, your _app.js should be
import localFont from '#next/font/local'
const surt = localFont({
src: './fonts/Surt-Normal-Bold.woff2',
variable: '--font-surt-bold',
})
export default function MyApp({ Component, pageProps }) {
return (
<main className={`${surt.variable} font-sans`}>
<Component {...pageProps} />
</main>
)
}
If you want to have different variants of the same font you can pass an array to the font src instead.
You can do it this way
const surt = localFont({
src: [
{
path: "./fonts/Surt-Normal-Regular.woff2",
weight: "400",
style: "normal",
},
{
path: "./fonts/Surt-Normal-Bold.woff2",
weight: "700",
style: "normal",
},
{
path: "./fonts/Surt-Normal-Black.woff2",
weight: "900",
style: "normal",
},
],
variable: "--font-surt-bold",
});
it is mentioned in the docs Here
Full credit goes to Lee Robinson and official v13 doc for my post.
The below will also help you with the new v13 /app directory.
1. Install next fonts
npm install #next/font
2. Download your fonts
In my case I downloaded the Poppins fonts and placed them in /public/fonts/
3. Link to your fonts file
As per docs you can edit your _app.js.
Link to your local fonts
assign a variable name
assign the class name to a parent html tag
In my case I am using the new app directory: /src/app/layout.tsx
import localFont from '#next/font/local'
const poppins = localFont({
src: [
{
path: '../../public/fonts/Poppins-Regular.ttf',
weight: '400'
},
{
path: '../../public/fonts/Poppins-Bold.ttf',
weight: '700'
}
],
variable: '--font-poppins'
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${poppins.variable} font-sans`}>
...
4. Reference in Tailwind config
As per docs you can now reference the new variable in your tailwind.config.js.
/** #type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/app/**/*.{js,ts,jsx,tsx}', // Note the addition of the `app` directory.
'./src/pages/**/*.{js,ts,jsx,tsx}',
'./src/components/**/*.{js,ts,jsx,tsx}'
],
theme: {
extend: {
fontFamily: {
sans: ['var(--font-poppins)']
}
}
},
plugins: []
}
If you console.log(surt),
const surt = localFont({
src: "../fonts/test.woff2",
variable: "--font-test-bold",
});
console.log("surt", surt);
you get this
// I used different font so values might be different
surt {
style: { fontFamily: "'__surt_899d56', '__surt_Fallback_899d56'" },
className: '__className_899d56',
variable: '__variable_899d56'
}
You dont need any configuration. All you have to do is apply this surt.className to an element and that font will be applied to all children.
<main className={surt.className}>
<Component {...pageProps} />
</main>
It works both for client components and app directory
how to apply this to any component in the project
I did the above configuration in _app.js and I did use any className or variable
import localFont from "#next/font/local";
const surt = localFont({
src: "../public/test.woff2",
variable: "--font-surt",
// I added this maybe fallback works but it did not
fallback: ["'Apple Color Emoji'"],
});
console.log("surt", surt);
export default function MyApp({ Component, pageProps }) {
return (
<main>
<Component {...pageProps} />
</main>
);
}
After you configure the tailwind.css the way you did, we should be able to use font-sans class anywhere in the project but no matter what I tried, tailwind does not inject it (it must be a bug). Workaround is like this. If you console the font, you will get this:
className: "__className_899d56"
style: {fontFamily: "'__surt_899d56', 'Apple Color Emoji','__surt_Fallback_899d56'"}
variable: "__variable_899d56"
I copied the style property from console (You could prop drill) and manually used it:
<p
className="'__className_899d56'"
style={{ fontFamily: "__surt_899d56" }}
>
with font testing
</p>

vue mpa fails to compile when adding css to <style> tags

I have an MPA app, where vue.js is used as a part of the application. I have a very simple test set up, here:
relevant parts of my template
....
<div id='app-basket-checkout'>
<h1>Better Be Here</h1>
</div>
....
pageBasketCheckout.js (essentially my app.js)
import Vue from 'vue'
import AppBasketCheckout from './BasketCheckout.vue'
import './dummyScss.css'
Vue.config.productionTip = false
new Vue({
render: h => h(AppBasketCheckout)
}).$mount('#app-basket-checkout')
component
<template>
<div id="app-basket-checkout">
{{msg}}
</div>
</template>
<script>
export default {
name: 'AppBasketCheckout',
components: {
},
data() {
return {
msg: 'Hello'
}
}
}
</script>
<style scoped>
</style>
So the above code renders just fine in my front end. I end up with an extra div that has hello printed inside, well done.
However when I add css to the style tag:
<template>
<div id="app-basket-checkout">
{{msg}}
</div>
</template>
<script>
export default {
name: 'AppBasketCheckout',
components: {
},
data() {
return {
msg: 'Hello'
}
}
}
</script>
<style scoped>
body {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.4;
}
</style>
This produces this error in chrome:
Uncaught Error: Cannot find module './BasketCheckout.vue?vue&type=style&index=0&id=2711cf65&scoped=true&lang=css&'
at webpackMissingModule (VM45512 BasketCheckout.vue:4)
at eval (VM45512 BasketCheckout.vue:4)
at Module../src/BasketCheckout.vue (pageBasketCheckout.bundle.js:40)
at __webpack_require__ (index.bundle.js:4312)
at eval (pageBasketCheckout.js:3)
at Module../src/pageBasketCheckout.js (pageBasketCheckout.bundle.js:29)
at __webpack_require__ (index.bundle.js:4312)
at checkDeferredModulesImpl (index.bundle.js:4453)
at webpackJsonpCallback (index.bundle.js:4435)
at pageBasketCheckout.bundle.js:9
Again this error only happens when adding css to the component. Here is my webpack.config.js:
const path = require('path');
const webpack = require('webpack')
const glob = require('glob')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
watch: true,
context: path.resolve(__dirname, 'uniquesite/uniquesite'),
mode: 'development',
entry: {
index: {
import: ['#babel/polyfill', './src/index.js'],
// dependOn: ['babel'],
},
pageProductDetails: {
import: ['#babel/polyfill', './src/pageProductDetails.js'],
dependOn: ['index'],
},
pageBasketCheckout: {
import: ['#babel/polyfill', './src/dummyScss.scss', './src/pageBasketCheckout.js'],
dependOn: ['index']
}
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'uniquesite/uniquesite/static/uniquesite/js/'),
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
alias: {
jquery: "jquery/src/jquery",
'jquery-ui': "jquery-ui-dist/jquery-ui.js",
boostrap: "bootstrap/dist/js/bootstrap.bundle.js"
}
},
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
},{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
}
]
}
};
You'll note I've also tried importing a dummy .css file to ensure the style loader works, as I've seen one more SO question with a similar problem that solved it that way. That didn't work for me however.
Update 1
My current thinking is that the problem has to be happening in the VueLoaderPlugin. That plugin is reponsible for splitting the script into distinct parts for template, logic, and style. It looks like the style is not actually making it into the bundle. See below.
"use strict";
eval(
"__webpack_require__.r(__webpack_exports__);
/* harmony import */
var _BasketCheckout_vue_vue_type_template_id_2711cf65___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
/*! ./BasketCheckout.vue?vue&type=template&id=2711cf65& */
\"./src/BasketCheckout.vue?vue&type=template&id=2711cf65&\"
);
/* harmony import */
var _BasketCheckout_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
/*! ./BasketCheckout.vue?vue&type=script&lang=js& */
\"./src/BasketCheckout.vue?vue&type=script&lang=js&\"
);
Object(function webpackMissingModule() {
var e = new Error(
\"Cannot find module './BasketCheckout.vue?vue&type=style&index=0&lang=css&'\"
); e.code = 'MODULE_NOT_FOUND';
throw e;
}());
/* harmony import */
var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(
/*! !../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */
\"../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"
);
/* normalize component */
var component = (
0,_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_3__.default
)(
_BasketCheckout_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__.default,
_BasketCheckout_vue_vue_type_template_id_2711cf65___WEBPACK_IMPORTED_MODULE_0__.render,
_BasketCheckout_vue_vue_type_template_id_2711cf65___WEBPACK_IMPORTED_MODULE_0__.staticRenderFns,
false,
null,
null,
null)
/* hot reload */
if (false) { var api; }
component.options.__file = \"src/BasketCheckout.vue\"
/* harmony default export */
__webpack_exports__[\"default\"] = (component.exports);
//# sourceURL=webpack:///./src/BasketCheckout.vue?"
);
Scoped CSS rules only apply to the current component (and its child components' root nodes).
You are mounting your Vue instance at #app-basket-checkout, which is already inside a <body> element.
You can style <body>, but do it using a global stylesheet that is imported in your app.js, not a subcomponent.
Alternatively, you can apply a class-based style at a low level node within your Vue instance and likely deliver your desired styles.

The correct way to bundle CSS modules

I have a weird thing happening when my css modules are exported with the * as styles becomes inaccessible when I bundle my code and use it in other repo's.
The response from styles when bundled:
{default: {... my class names} }
When I change my code to import styles from '...' it works when bundled because styles is the default but fails the tests because styles does not have access to the named exports.
rollup config.js
import resolve from '#rollup/plugin-node-resolve'
import commonjs from '#rollup/plugin-commonjs'
import typescript from 'rollup-plugin-typescript2'
import { terser } from 'rollup-plugin-terser'
import postcss from 'rollup-plugin-postcss'
import postCssConfig from '#cinch-labs/postcss-config'
import pkg from './package.json'
import { designTokens, toJSON } from './src/tokens'
const extensions = ['.ts', '.tsx']
// stylelint does work but the postcss one needed to be removed
const postcssPlugins = postCssConfig(toJSON(designTokens)).filter(
({ postcssPlugin }: { postcssPlugin: string }) => postcssPlugin !== 'stylelint',
)
export default [
{
input: './src/index.ts',
output: [
{
file: pkg.main,
format: 'cjs',
},
{
file: pkg.module,
format: 'es',
},
],
plugins: [
postcss({
modules: true,
extract: false,
syntax: 'postcss-scss',
plugins: postcssPlugins,
use: ['sass'],
}),
resolve({
extensions,
}),
commonjs(),
typescript({ tsconfig: 'tsconfig.rollup.json' }),
terser(),
],
external: ['react', 'react-dom'],
},
]
test.component.tsx
import React from 'react'
import classNames from 'classnames'
// I expected the bundler to resolve this for me...
import * as styles from './text.module.scss'
import { TextProps } from './text.types'
export const Text: React.FC<TextProps> = ({
children,
fontSize = 'm',
fontWeight = 'medium',
fontStyle = 'normal',
lineHeight = 'body',
element = 'p',
className,
...props
}) => {
const HtmlEl = element
const classes = classNames(
{
[styles[`textSize${fontSize.toUpperCase()}`]]: fontSize,
[styles[`textWeight${fontWeight.toUpperCase()}`]]: fontWeight,
[styles[`textLineHeight${lineHeight.toUpperCase()}`]]: lineHeight,
[styles[`textFontStyle${fontStyle.toUpperCase()}`]]: fontStyle,
},
className,
)
// classes returns undefined when bundled because of commonjs format.
return (
<HtmlEl className={classes} {...props}>
{children}
</HtmlEl>
)
}
I know this is due to the way common JS works however I would expect for the import * as styles to work. When I change it to import styles from './text.module.scss' it works fine when bundled but does not work in tests.
Using import * as styles from './text.module.scss' you are importing the styles as a named export.
Since this also returns {default: {... my class names} }, you can use styles.default instead, or, perhaps, assign it to a new variable like
const style = styles.default
Fixing this issue was by doing import styles from 'path name' and then installing jest-css-modules to map the styles object in my test.
https://www.npmjs.com/package/jest-css-modules
for me to compile and include with rollup.js the scss into the bundle/build worked adding:
plugins: [
postcss({
modules: true,
extract: false,
syntax: 'postcss-scss',
use: ['sass'],
}),
],
Hope this will help someone else in this journey :)

Font is not loading when using styled-components with react and storybook

I'm building app based on React, Typescript with build on Webpack. So basically everything is working ok when using webpack or webpack-dev-server. But then I wanted to use storybook, and I want to put some fonts that are inside my project directory. And in sotrybook I can't see my fonts, they're not working for some reason - I think that could be webpack related.
I'm using this to load fonts:
//globalStyle.ts
import { createGlobalStyle } from "styled-components";
import bender from "../resources/fonts/Bender.otf";
const GlobalStyle = createGlobalStyle`
#font-face {
font-family: 'Bender';
src: url(${bender});
font-weight: 400;
font-style: normal;
}
So using this in code as <GlobalStyle/> with default webpack build is working. But to apply the effect for each story-book component I use this decorator:
import * as React from "react";
import GlobalStyle from "../src/styles/globalStyle";
import mainTheme from "../src/styles/mainTheme";
import { ThemeProvider } from "styled-components";
import { configure, addDecorator } from "#storybook/react";
const req = require.context("../src/", true, /\.stories\.tsx$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
const withGlobal = storyFn => {
return (
<ThemeProvider theme={mainTheme}>
<GlobalStyle />
{storyFn()}
</ThemeProvider>
);
};
addDecorator(withGlobal);
configure(loadStories, module);
Going to the story-book inspector I see that style has loaded, but looking at #font-face fields i see something like
#font-face {
src: url()
/**/
}
So the URL of font is empty. I tried replacing in my code this import to require(_path_) and the src: url() was filled but then no file under this address.
I am using custom storybook webpack config:
const path = require('path');
module.exports = {
stories: ['../src/**/*.stories.tsx'],
addons: ['#storybook/preset-typescript'],
webpackFinal: async config => {
config.module.rules.push(
{
test: /\.(ts|tsx)$/,
use:
[
{ loader: require.resolve('ts-loader') },
{ loader: require.resolve('react-docgen-typescript-loader') },
],
},
{
test: /\.(png|woff|woff2|eot|ttf|svg|otf)$/,
use: [
{
loader: require.resolve('url-loader'),
},
],
},
);
config.output.publicPath = "http://localhost:9001/"
config.resolve.extensions.push('.js', '.jsx', '.ts', '.tsx');
return config;
},
}
I had a similar issue because my font wasn't being downloaded so I added the font link to a file preview-head.html inside .storybook folder.
Check https://storybook.js.org/docs/configurations/add-custom-head-tags/

Imported styles object is empty in Jest

I have a component:
import React from 'react';
import * as styles from './RedComponent.css';
class RedComponent extends React.Component {
render () {
return <div className={ styles.red }></div>;
}
}
This is the test case:
describe('Test suite', () => {
test('RedComponent tests', () => {
const wrapper = shallow(<RedComponent />);
});
console.log(wrapper.debug());
gives
<div className={[undefined]}></div>
instead of
<div className="RedComponent__red"></div>
If I console the imported styles I get
console.log(styles); // {default: {}}
This is only in Jest test cases. Style is not undefined when the app renders in browser.
My jest config:
{
"moduleFileExtensions": [
"js"
],
"moduleDirectories": [
"node_modules"
],
"moduleNameMapper": {
"\\.(css|less)$": "identity-obj-proxy"
},
"setupFiles": [
"./test-setup.js"
],
"collectCoverageFrom": [
"src/**/*.{js}",
"!**/node_modules/**"
],
"testEnvironment": "node",
"transform": {
"^.+\\.js$": "babel-jest",
"\\.(md|ttf|txt|eot|ico|otf|svg|png|gif|woff2|woff|jpeg)$": "./file-transformer.js"
}
}
Using jest v21.2.1, identity-obj-proxy v3.0.0 and React v16.0.0.
You have to change this line
import * as styles from './RedComponent.css';
to this:
import styles from './RedComponent.css';
I assume that you are using css-loader or similar and this is just how the loader works.
Maybe worths to check the example:
https://github.com/keyanzhang/jest-css-modules-example/
I think your moduleNameMapper should be like this:
"^.+\\.(css|less)$": "identity-obj-proxy"
Create a jest/identity-obj-proxy-esm.js file with the following content:
// This works around the fact we use ES named exports for styles, e.g.:
// import * as styles from './styles.scss'.
// https://github.com/keyanzhang/identity-obj-proxy/issues/8
module.exports = new Proxy(
{},
{
get: function getter(target, key) {
if (key === '__esModule') {
// True instead of false to pretend we're an ES module.
return true;
}
return key;
},
},
);
Edit jest.config.js:
// jest.config.js
module.exports = {
...
moduleNameMapper: {
...
'\\.(css|scss)$': '<rootDir>/jest/identity-obj-proxy-esm.js',
}
};
🏆 João Vieira and https://github.com/keyz/identity-obj-proxy/issues/8#issuecomment-430241345

Resources