nandorojo / dripsy

šŸ· Responsive, unstyled UI primitives for React Native + Web.
https://dripsy.xyz
MIT License
1.99k stars 77 forks source link

load fonts in NextJS #193

Closed rodbs closed 1 year ago

rodbs commented 2 years ago

I cannot manage to load web fonts in NextJS following what it's said here: https://www.dripsy.xyz/fonts#web-optimizations

Normally you need to load them within styles (tailwind), right?

  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 100 900;
  font-display: optional;
  src: url(/fonts/ibm-plex-sans-var.woff2) format('woff2');
}

But with Dripsy, DripsyProvider is in charge of it?

This my _document.tsx

       <link
            rel="preload"
            href="/fonts/Inter/Inter-Regular.ttf"
            as="font"
            crossOrigin=""
          />
          <link
            rel="preload"
            href="/fonts/Inter/Inter-bold.ttf"
            as="font"
            crossOrigin=""
          />

theme.tsx


export const fontName = 'Inter'

const webFont = (font: string) =>
  Platform.select({
    web: `${font}, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, Inter-serif`,
    default: font,
  })

const theme = makeTheme({
  colors: darkColors,
  customFonts: {
    [fontName]: {
      bold: webFont(fontName),
      default: webFont(fontName),
      normal: webFont(fontName)
    },
  },
  fonts: {
    root: fontName,
  },
  text: {
    default: {
      fontSize: 26,
    },
    body: {},
    bold: {
      fontWeight: 'bold',
    }, 
  },
})
`` `
nandorojo commented 2 years ago

oh I think the missing piece is the fontface CSS code, which gets injected in your document as well.

rodbs commented 2 years ago

And where is it supposed to go?

For Native, you wrap the DripsyProvider using useFonts in Fonts to load the fonts.

export default function App() {
  return (
    <Fonts>
      <DripsyProvider theme={theme}>

Shouldn't there be something similar using fontface for nextJS?

nandorojo commented 2 years ago

It would go in _document. It's a bit more complicated than that unfortunately. I'll try to add an example when I have some time. You'd basically add a new <style> tag in getInitialProps

rodbs commented 2 years ago

I've seen these two posts; I guess you mean something like this? https://github.com/vercel/next.js/discussions/13533 https://github.com/ben-rogerson/twin.examples/blob/b52ac511ebf221471a01fea1c77d90b19a6eb5dc/next-stitches-typescript/pages/_document.tsx

But also here here they recomnnend not using getInitialProps. Why? https://nextjs.org/docs/advanced-features/custom-document To prepare for [React 18](https://nextjs.org/docs/advanced-features/react-18), we recommend avoiding customizing getInitialProps and renderPage, if possible.

nandorojo commented 2 years ago

interestingā€¦in my app, i extend the CSS reset string supplied by expo/next-adapter/_document

rodbs commented 2 years ago

I think the issue about using getInitalPros on React 18 is fixed: https://github.com/vercel/next.js/releases/tag/v12.1.4 https://github.com/vercel/next.js/pull/35760

I've taken this _document.js from expo and it seems to work: https://docs.expo.dev/guides/using-nextjs/#customizing-the-document


import { getInitialProps } from '@expo/next-adapter/document';
import Document, { Head, Main, NextScript } from 'next/document';
import React from 'react';

const ibm1 = `@font-face {
    font-family: 'IBM Plex Sans';
    font-style: normal;
    font-weight: 100 900;
    font-display: optional;
    src: url(/fonts/ibm-plex-sans-var.woff2) format('woff2');
  }`

class CustomDocument extends Document {
  render() {
    return (
      <html>
        <Head>
          <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
           <link
            rel="preload"
            href="/fonts/ibm-plex-sans-var.woff2"
            as="font"
            type="font/woff2"
            crossOrigin="anonymous"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

// Import the getInitialProps method and assign it to your component to ensure the react-native-web styles are used.
CustomDocument.getInitialProps = getInitialProps;

// OR...

CustomDocument.getInitialProps = async props => {
  const result = await getInitialProps(props);
  // Mutate result...
  return {
    ...result,
    styles: (
      <>
        <style>{ibm1}</style>
      </>
    ),
  };
};

export default CustomDocument;

But the fonts don't load. Might it be because the way the theme is loded? (I've tried also with other fonts, and same thing ..)

export const fontName = 'IBM Plex Sans'

const webFont = (font: string) =>
  Platform.select({
    web: `${font}, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, Inter-serif`,
    default: font,
  })

const theme = makeTheme({
  colors: darkColors,
  customFonts: {
    [fontName]: {
      bold: webFont(fontName),
      default: webFont(fontName),
      normal: webFont(fontName),
      '400': webFont(fontName),
      '500': webFont(fontName),
      '600': webFont(fontName),
      '700': webFont(fontName),
      '800': webFont(fontName),
      '900': webFont(fontName),
    },
  },

  fonts: {
    root: fontName,
  },
  text: {
    default: {
      fontSize: 26,
    },
    body: {},
    bold: {
      fontWeight: 'bold',
    },
    boldWithRoot: {
      fontWeight: 'bold',
      fontFamily: 'root', // this isn't needed, but it illustrates it
    }, 
  },
})
...
rodbs commented 2 years ago

I think I can make it work. It's a problem with require and webpack It's explained here: https://github.com/expo/expo/issues/9135

Awesome, thanks for digging into this. I think I might know why this is happening, inline require might be handled as dynamic import in Webpack. If this happens, the require method returns a promise instead of the icon resource itself. This is something we can investigate and work on, thanks! The workaround looks great, it shouldn't behave differently on native as well so good find!

Basically I'm doing this:

export const useResources = () => {
  const [isFontReady, setIsFontReady] = useState(false);

  const loadFontAsync = async () => {
    try {
      await Font.loadAsync({
        HindRegular: {
          uri: YOUR_FONT_FILE_HERE as any,
          display: Font.FontDisplay.SWAP,
        }
      });
    } catch (error) {
      console.log('Font Load Error:', error)
    } finally {
      setIsFontReady(true);
    }
  }

  useEffect(() => {
    loadFontAsync();
  }, []);

  return {
    isFontReady
  }
};

But now I've found another issue and it's that SSR is not working, I guess because of the setIsFontReady . Do you know how to fix it?

Another issue I 'm facing is that I have to place the fonts in two locations: ones in app/... for React Native and others in public for NextJs. Could it be shared somehow (common folder)?

nandorojo commented 2 years ago

I just put it in public and load it using CSS fontface in _document.tsx, along woth <link> tags.

nandorojo commented 1 year ago

I added an example to Solito with custom fonts using Dripsy, Next.js and Expo font:

npx create-solito-app@latest -t with-custom-font

You can see it here.