简体   繁体   中英

NextJs Tailwind build (purge) removes all styling

In my current NextJS project, I am using Tailwind as a CSS framework. When I run yarn dev , everything works fine, but whenever I run yarn build followed by a yarn start , all of my CSS seems to be purged as the layout of my page is completely different.

Before: 在此处输入图像描述

After: 在此处输入图像描述

My tailwind.config.js file:

    /* eslint-disable global-require */

const defaultTheme = require('tailwindcss/defaultTheme');

module.exports = {
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    screens: {
      sm: '640px',
      md: '768px',
      max_md: { max: '767px' },
      lg: '1024px',
      xl: '1536px',
    },
    colors: {
      primary: '#f2a53f',
      white: '#fff',
    },
    fontFamily: {
      title: ['Dancing Script', 'Times New Roman', 'sans-serif'],
      sans: ['Roboto', ...defaultTheme.fontFamily.sans],
    },
    textShadow: {
      default: '1px 1px 3px #000',
    },
    zIndex: {
      1: -1,
    },
    extend: {
      height: {
        128: '32rem',
      },
      margin: {},
    },
  },
  variants: {
    extend: {},
  },
  plugins: [require('tailwindcss-textshadow')],
};

postcss.config.js file:

const purgecss = [
  '@fullhuman/postcss-purgecss',
  {
    content: [
      './pages/**/*.{js,jsx,ts,tsx}',
      './components/**/*.{js,jsx,ts,tsx}',
    ],
    defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
  },
];

module.exports = {
  plugins: [
    'tailwindcss',
    process.env.NODE_ENV === 'production' ? purgecss : undefined,
  ],
};

_app.tsx:

import Head from 'next/head';
import { ApolloProvider } from '@apollo/client';
import { AppProps } from 'next/app';
import { useApollo } from '../../apollo/client';

import '../styles/globals.css';

const MyApp = ({ Component, pageProps }: AppProps) => (
  <ApolloProvider client={useApollo(pageProps.initialApolloState)}>
    <Component {...pageProps} />
  </ApolloProvider>
);

export default MyApp;

globals.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

@font-face {
  font-family: 'Dancing Script';
  font-style: medium;
  font-weight: 500;
  font-display: swap;
  src: local('Dancing Script'),
    url(/fonts/DancingScript-Medium.tff) format('tff');
}
body {
  margin: 0 !important;
}

package.json dependencies:

"dependencies": {
    "@apollo/client": "3.3.12",
    "@apollo/react-hooks": "4.0.0",
    "@contentful/rich-text-react-renderer": "14.1.2",
    "@contentful/rich-text-types": "14.1.2",
    "apollo-cache-inmemory": "1.6.6",
    "apollo-client": "2.6.10",
    "apollo-link-http": "1.5.17",
    "autoprefixer": "10.2.5",
    "clsx": "1.1.1",
    "contentful": "8.2.0",
    "graphql": "15.5.0",
    "graphql-tag": "2.11.0",
    "next": "10.0.9",
    "next-with-apollo": "5.1.1",
    "postcss": "8.2.8",
    "react": "17.0.1",
    "react-dom": "17.0.1",
    "react-icons": "4.2.0",
    "tailwindcss": "2.0.4",
    "tailwindcss-textshadow": "2.1.3"
  },
  "devDependencies": {
    "@commitlint/cli": "12.0.1",
    "@commitlint/config-conventional": "12.0.1",
    "@fullhuman/postcss-purgecss": "4.0.3",
    "@types/node": "14.14.35",
    "@types/react": "17.0.3",
    "@types/react-dom": "17.0.2",
    "@typescript-eslint/eslint-plugin": "4.18.0",
    "@typescript-eslint/parser": "4.18.0",
    "add": "2.0.6",
    "commitizen": "4.2.3",
    "cz-conventional-changelog": "3.3.0",
    "eslint": "7.22.0",
    "eslint-config-airbnb": "18.2.1",
    "eslint-config-prettier": "8.1.0",
    "eslint-import-resolver-typescript": "2.4.0",
    "eslint-plugin-import": "2.22.1",
    "eslint-plugin-jsx-a11y": "6.4.1",
    "eslint-plugin-prettier": "3.3.1",
    "eslint-plugin-react": "7.22.0",
    "eslint-plugin-react-hooks": "4.2.0",
    "husky": "5.1.3",
    "lint-staged": "10.5.4",
    "postcss-preset-env": "6.7.0",
    "prettier": "2.2.1",
    "typescript": "4.2.3",
    "yarn": "1.22.10"
  },
  "config": {
    "commitizen": {
      "path": "cz-conventional-changelog"
    }
  }

If there is anything that seems off, feel free to let me know. I have looked at other issues online but did not find and solution to my problem.

Double check the paths in tailwind.config.js . If your components are not in the components directory, then you need to update the purge paths to reflect this.

I think I see the issue. @fullhuman/postcss-purgecss has been bundled with tailwindcss since v1.4 or so. Since it's manually declared in your postcss file as well, it's being executed twice which is wiping all of your styles. Also the fact that it only wipes your styles in production indicates that it is the culprit as execution of that particular package is dependent on process.env.NODE_ENV===production . Removing it from your postcss file should resolve your issue since it is already being executed under the hood by the tailwindcss bundle.

Here is my postcss with tailwindcss@2.0.3 for comparison:

postcss.config.js

module.exports = {
    plugins: [
        'postcss-import',
        'tailwindcss',
        'postcss-nesting',
        'postcss-flexbugs-fixes',
        [
            'postcss-preset-env',
            {
                autoprefixer: {
                    flexbox: 'no-2009'
                },
                stage: 3,
                features: {
                    'custom-properties': false,
                    'nesting-rules': true
                }
            }
        ]
    ]
};

One other possibility is that you aren't wrapping the paths/file-types targeted for purging within a top-level content object. From the code provided for your tailwind.config.js file:

module.exports = {
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  //...
}

Here is my tailwind.config.js for cross-comparison

const defaultTheme = require('tailwindcss/defaultTheme');

module.exports = {
    important: true,
    purge: {
        content: [
            './components/**/*.{js,ts,jsx,tsx}',
            './pages/**/*.{js,ts,jsx,tsx}'
        ],
        options: {
            safelist: {
                standard: ['outline-none']
            }
        }
    },
    darkMode: 'class',
    theme: {
        lineClamp: {
            1: 1,
            2: 2,
            3: 3,
            4: 4
        },
        extend: {
            zIndex: {
                '-10': '-10',
                100: '100',
                150: '150'
            },
            maxWidth: {
                '9xl': '121rem', // 1936px
                '8xl': '96rem' // 1536px
            },
            screens: {
                xs: '375px',
                sm: '640px',
                md: '768px',
                lg: '1024px',
                xl: '1280px',
                '2xl': '1440px',
                '3xl': '1920px'
            },
            transitionDuration: {
                0: '0ms',
                300: '300ms',
                500: '500ms',
                700: '700ms',
                1000: '1000ms'
            },
            rotate: {
                0: '0deg',
                45: '45deg',
                90: '90deg',
                125: '125deg',
                180: '180deg',
                270: '270deg',
                360: '360deg'
            },
            fontFamily: {
                header: ['goudy-bookletter-1911', 'serif'],
                poppins: ['poppins', 'sans-serif'],
                somaRoman: ['neue-haas-grotesk-text', 'sans-serif'],
                somaDisplay: ['neue-haas-grotesk-display', 'sans-serif'],
                sans: ['Inter var', ...defaultTheme.fontFamily.sans]
            },
            colors: {
                'reddit-0': 'var(--reddit-0)',
                'reddit-1': 'var(--reddit-1)',
                'reddit-2': 'var(--reddit-2)',
                'reddit-3': 'var(--reddit-3)',
                'reddit-4': 'var(--reddit-4)',
                'primary-0': 'var(--primary-0)',
                'primary-1': 'var(--primary-1)',
                'primary-2': 'var(--primary-2)',
                'primary-3': 'var(--primary-3)',
                'primary-4': 'var(--primary-4)',
                'primary-5': 'var(--primary-5)',
                'primary-6': 'var(--primary-6)',
                'primary-7': 'var(--primary-7)',
                'primary-8': 'var(--primary-8)',
                'primary-9': 'var(--primary-9)',
                'secondary-0': 'var(--secondary-0)',
                'secondary-1': 'var(--secondary-1)',
                'secondary-2': 'var(--secondary-2)',
                'accents-0': 'var(--accents-0)',
                'accents-1': 'var(--accents-1)',
                'accents-2': 'var(--accents-2)',
                'accents-3': 'var(--accents-3)',
                'accents-4': 'var(--accents-4)',
                'accents-5': 'var(--accents-5)',
                'accents-6': 'var(--accents-6)',
                'accents-7': 'var(--accents-7)',
                'accents-8': 'var(--accents-8)',
                'accents-9': 'var(--accents-9)',
                'theme-0': 'var(--theme-0)',
                'theme-1': 'var(--theme-1)',
                lightBlue: {
                    0: '#E3F8FF',
                    100: '#B3ECFF',
                    200: '#81DEFD',
                    300: '#5ED0FA',
                    400: '#40C3F7',
                    500: '#2BB0ED',
                    600: '#1992D4',
                    700: '#127FBF',
                    800: '#0B69A3',
                    900: '#035388'
                },
                cyan: {
                    0: '#E0FCFF',
                    100: '#BEF8FD',
                    200: '#87EAF2',
                    300: '#54D1DB',
                    400: '#38BEC9',
                    500: '#2CB1BC',
                    600: '#14919B',
                    700: '#0E7C86',
                    800: '#0A6C74',
                    900: '#044E54'
                },
                rojo: {
                    0: '#610316',
                    100: '#8A041A',
                    200: '#AB091E',
                    300: '#CF1124',
                    400: '#E12D39',
                    500: '#EF4E4E',
                    600: '#F86A6A',
                    700: '#FF9B9B',
                    800: '#FFBDBD',
                    900: '#FFE3E3'
                },
                rosado: {
                    0: '#620042',
                    100: '#870557',
                    200: '#A30664',
                    300: '#BC0A6F',
                    400: '#DA127D',
                    500: '#E8368F',
                    600: '#F364A2',
                    700: '#FF8CBA',
                    800: '#FFB8D2',
                    900: '#FFE3EC'
                },
                amarillo: {
                    0: 'hsl(15, 86%, 30%)',
                    100: 'hsl(22, 82%, 39%)',
                    200: 'hsl(29, 80%, 44%)',
                    300: 'hsl(36, 77%, 49%)',
                    400: 'hsl(42, 87%, 55%)',
                    500: 'hsl(44, 92%, 63%)',
                    600: 'hsl(48, 94%, 68%)',
                    700: 'hsl(48, 95%, 76%)',
                    800: 'hsl(48, 100%, 88%)',
                    900: 'hsl(49, 100%, 96%)'
                },
                verdeAzulado: {
                    // blueish-green === teal (espanol)
                    0: 'hsl(170, 97%, 15%)',
                    100: 'hsl(168, 80%, 23%)',
                    200: 'hsl(166, 72%, 28%)',
                    300: 'hsl(164, 71%, 34%)',
                    400: 'hsl(162, 63%, 41%)',
                    500: 'hsl(160, 51%, 49%)',
                    600: 'hsl(158, 58%, 62%)',
                    700: 'hsl(156, 73%, 74%)',
                    800: 'hsl(154, 75%, 87%)',
                    900: 'hsl(152, 68%, 96%)'
                },
                redditRed: '#FF4500',
                redditNav: '#1A1A1B',
                redditSearch: '#272729',
                redditBG: '#141415'
            },
            keyframes: {
                wiggle: {
                    '0%, 100%': { transform: 'rotate(-3deg)' },
                    '50%': { transform: 'rotate(3deg)' }
                },
                hero: {
                    transform: 'translate3d(0px, 0px, 0px)'
                }
            },
            animation: {
                wiggle: 'wiggle 10s ease-in-out infinite',
                hero: 'hero 1s ease-in-out infinite',
                slowPing: 'pulse 10s cubic-bezier(0, 0, 0.2, 1) infinite'
            },
            width: {
                82: '20.5rem',
                100: '25rem',
                200: '50rem',
                '8xl': '96rem'
            },
            height: {
                75: '75vh'
            },
            spacing: {
                7: '1.75rem',
                14: '3.5rem',
                18: '4.5rem',
                25: '6.25rem',
                26: '6.5rem',
                28: '7rem',
                44: '11rem',
                82: '20.5rem',
                100: '25rem',
                104: '26rem',
                156: '39rem'
            },
            boxShadow: {
                'outline-2': '0 0 0 2px var(--accents-0)',
                magical:
                    'rgba(0, 0, 0, 0.02) 0px 30px 30px, rgba(0, 0, 0, 0.03) 0px 0px 8px, rgba(0, 0, 0, 0.05) 0px 1px 0px',
                cardHover:
                    '0 4px 4.1px rgba(0, 0, 0, 0.012),0 4.9px 5.8px rgba(0, 0, 0, 0.018),0 6.3px 8.4px rgba(0, 0, 0, 0.029),0 8.8px 12.9px rgba(0, 0, 0, 0.05),0 15px 23px rgba(0, 0, 0, 0.11)'
            }
        },
        variants: {
            padding: [
                'responsive',
                'group-hover',
                'hover',
                'focus',
                'even',
                'odd',
                'first',
                'last'
            ],
            textColor: [
                'responsive',
                'group-hover',
                'hover',
                'focus',
                'even',
                'first',
                'last',
                'odd'
            ],
            backgroundColor: [
                'responsive',
                'group-hover',
                'hover',
                'focus',
                'even',
                'first',
                'last',
                'odd'
            ],
            display: ['responsive', 'hover', 'group-hover'],
            visibility: ['responsive', 'hover', 'group-hover'],
            transitionDuration: ['responsive', 'hover', 'group-hover'],
            gridColumn: ['responsive', 'hover', 'first', 'odd', 'even'],
            extend: {
                ringWidth: [
                    'responsive',
                    'hover',
                    'active',
                    'focus',
                    'group-hover'
                ],
                ringColor: [
                    'responsive',
                    'hover',
                    'active',
                    'focus',
                    'group-hover'
                ],
                fontSize: ['responsive', 'last', 'first', 'hover', 'focus'],
                stroke: ['responsive', 'hover', 'focus', 'group-hover'],
                fill: ['responsive', 'hover', 'focus', 'group-hover'],
                gridTemplateColumns: [
                    'responsive',
                    'last',
                    'first',
                    'hover',
                    'focus'
                ],
                animation: [
                    'responsive',
                    'hover',
                    'focus',
                    'motion-safe',
                    'motion-reduce'
                ],
                transitionProperty: [
                    'responsive',
                    'hover',
                    'focus',
                    'motion-safe',
                    'motion-reduce'
                ],
                transitionDuration: ['responsive', 'hover', 'focus'],
                transitionTimingFunction: ['responsive', 'hover', 'focus'],
                transitionDelay: ['responsive', 'hover', 'focus'],
                scale: [
                    'responsive',
                    'hover',
                    'focus',
                    'active',
                    'group-hover'
                ],
                rotate: [
                    'responsive',
                    'hover',
                    'focus',
                    'active',
                    'group-hover'
                ]
            }
        }
    },
    plugins: [
        require('tailwindcss-line-clamp'),
        require('@tailwindcss/typography'),
        require('@tailwindcss/forms'),
        require('@tailwindcss/aspect-ratio')
    ]
};

If you're wondering why I use the postcss-import package before tailwindcss in my postcss.config.js, it's for separation of concerns and cleaner code. I have a top-level styles directory containing index.css , base.css , utilities.css , components.css , and chrome-bug.css . Their contents are as follows:

index.css

@import 'tailwindcss/base';
@import './base.css';

@import 'tailwindcss/components';
@import './components.css';

@import 'tailwindcss/utilities';
@import './utilities.css';

components.css

.fit {
    min-height: calc(100vh - 88px);
}

utilities.css

#tsparticles {
    position: fixed;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    margin: 0;
}

.skeleton {
    display: block;
    width: 100%;
    border-radius: 5px;

    background-image: linear-gradient(
        270deg,
        var(--accents-1),
        var(--accents-2),
        var(--accents-2),
        var(--accents-1)
    );
    background-size: 400% 100%;
    animation: loading 8s ease-in-out infinite;
}

@keyframes loading {
    0% {
        background-position: 200% 0;
    }
    100% {
        background-position: -200% 0;
    }
}

base.css

#__next {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

:root {
    --reddit-0: hsl(240, 2%, 8%);
    --reddit-1: hsl(240, 2%, 10%);
    --reddit-2: hsl(240, 3%, 16%);
    --reddit-3: hsl(16, 100%, 50%);
    --primary-0: hsl(209, 61%, 16%);
    --primary-1: hsl(211, 39%, 23%);
    --primary-2: hsl(209, 34%, 30%);
    --primary-3: hsl(209, 28%, 39%);
    --primary-4: hsl(210, 22%, 49%);
    --primary-5: hsl(209, 23%, 60%);
    --primary-6: hsl(211, 27%, 70%);
    --primary-7: hsl(210, 31%, 80%);
    --primary-8: hsl(212, 33%, 89%);
    --primary-9: hsl(210, 36%, 96%);
    --secondary-0: #d7be69;
    --secondary-1: #486581;
    --secondary-2: #9fb3c8;
    --accents-0: hsl(195, 7%, 11%);
    --accents-1: hsl(140, 2%, 26%);
    --accents-2: hsl(0, 0%, 49%);
    --accents-3: hsl(0, 0%, 64%);
    --accents-4: hsl(0, 1%, 81%);
    --accents-5: hsl(0, 0%, 89%);
    --accents-6: hsl(50, 21%, 95%);
    --theme-0: hsl(210, 24%, 84%);
    --theme-1: hsl(209, 28%, 39%);
    @apply overflow-x-hidden;
}

*,
*:before,
*:after {
    box-sizing: inherit;
}

/* Remove Safari input shadow on mobile */
textarea,
input:matches([type='email'], [type='number'], [type='password'], [type='search'], [type='tel'], [type='text'], [type='url']) {
    -webkit-appearance: none;
}

html {
    height: 100%;
    box-sizing: border-box;
    touch-action: manipulation;
    font-feature-settings: 'case' 1, 'rlig' 1, 'calt' 0;
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
    -webkit-tap-highlight-color: transparent;
    -moz-osx-font-smoothing: grayscale;
    --webkit-text-size-adjust: none;
    text-size-adjust: none;
    scroll-behavior: smooth;
}

html,
body {
    font-family: var(--font-sans);
    text-rendering: optimizeLegibility;
    ::--webkit-font-smoothing: antialiased;
    ::--moz-osx-font-smoothing: grayscale;
    background-color: var(--reddit-0);
    color: var(--text-accents-6);
}

body {
    position: relative;
    min-height: 100%;
    margin: 0;
    scrollbar-width: none;
    scrollbar-color: var(--primary-0) var(--primary-9); /* scroll thumb and track */
}

body::-webkit-scrollbar {
    display: thin; /* Hide scrollbar for Chrome, Safari and Opera https://www.w3schools.com/howto/howto_css_hide_scrollbars.asp */
    width: 10px;
}

body::-webkit-scrollbar-track {
    background: var(--accents-7); /* color of the tracking area */
}

body::-webkit-scrollbar-thumb {
    background-color: var(
        --secondary-0
    ); /* color of the scroll thumb */
    border-radius: 0px; /* roundness of the scroll thumb */
    border: 3px var(--secondary-0); /* creates padding around scroll thumb */
}

a {
    -webkit-tap-highlight-color: hsla(0, 0%, 0%, 0);
}

.animated {
    --webkit-animation-duration: 1s;
    --animation-duration: 1s;
    -animation-duration: 1s;
    --webkit-animation-fill-mode: both;
    animation-fill-mode: both;
}

.fadeIn {
    -webkit-animation-name: fadeIn;
    animation-name: fadeIn;
}

@-webkit-keyframes fadeIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

chrome-bug.css

/**
 * Chrome has a bug with transitions onLoad since 2012!
 *
 * To prevent a "pop" of content, you have to disable all transitions until
 * the page is done loading.
 *
 * https://lab.laukstein.com/bug/input
 * https://twitter.com/timer150/status/1345217126680899584
 */
body.loading * {
    transition: none !important;
}

The chrome-bug class is conditionally handled via a useEffect hook in _app.tsx and is called in the body of _document.tsx

_app.tsx

import '@/styles/index.css';
import '@/styles/chrome-bug.css';
import 'keen-slider/keen-slider.min.css';

import App, {
    AppContext,
    AppInitialProps,
    AppProps,
    NextWebVitalsMetric
} from 'next/app';
import { useRouter } from 'next/router';
import { ApolloProvider } from '@apollo/client';
import { useEffect, FC } from 'react';
import {
    useApollo,
    initializeApollo,
    addApolloState
} from '@/lib/apollo';
import * as gtag from '@/lib/analytics';
import { MediaContextProvider } from '@/lib/artsy-fresnel';
import { Head } from '@/components/Head';
import { GTagPageview } from '@/types/analytics';
// import { AppLayout } from '@/components/Layout';
import {
    DynamicNavQuery,
    DynamicNavQueryVariables,
    DynamicNavDocument,
    MenuNodeIdTypeEnum
} from '@/graphql/generated/graphql';

const Noop: FC = ({ children }) => <>{children}</>;

function NextApp({
    Component,
    pageProps: { ...pageProps }
}: AppProps<typeof NextApp.getInitialProps>) {
    const apolloClient = useApollo(pageProps);
    const LayoutNoop = (Component as any).LayoutNoop || Noop;
    const router = useRouter();
    useEffect(() => {
        document.body.classList?.remove('loading');
    }, []);
    useEffect(() => {
        const handleRouteChange = (url: GTagPageview) => {
            gtag.pageview(url);
        };
        router.events.on('routeChangeComplete', handleRouteChange);
        return () => {
            router.events.off('routeChangeComplete', handleRouteChange);
        };
    }, [router.events]);
    return (
        <>
            <Head />
            <ApolloProvider client={apolloClient}>
                <MediaContextProvider>
                    <LayoutNoop pageProps={pageProps}>
                        <Component {...pageProps} />
                    </LayoutNoop>
                </MediaContextProvider>
            </ApolloProvider>
        </>
    );
}

NextApp.getInitialProps = async (
    appContext: AppContext
): Promise<AppInitialProps> => {
    const pageProps = await App.getInitialProps(appContext);

    const graphqlClient = initializeApollo();
    const dynamicNav = await graphqlClient.query<
        DynamicNavQuery,
        DynamicNavQueryVariables
    >({
        query: DynamicNavDocument,
        variables: {
            idHead: 'Header',
            idTypeHead: MenuNodeIdTypeEnum.Name,
            idFoot: 'Footer',
            idTypeFoot: MenuNodeIdTypeEnum.Name
        }
    });
    return addApolloState(graphqlClient, {
        pageProps: {
            ...pageProps.pageProps,
            Header: dynamicNav.data.Header,
            Footer: dynamicNav.data.Footer
        }
    });
};

export function reportWebVitals(metric: NextWebVitalsMetric) {
    console.debug('vital: ', metric);
}

export default NextApp;

_document.tsx

import Document, {
    Head,
    Html,
    Main,
    NextScript,
    DocumentContext
} from 'next/document';
import { mediaStyles } from '@/lib/artsy-fresnel';
const GA_TRACKING_ID = 'G-RJQZB1C7TR';

class MyDocument extends Document {
    static async getInitialProps(ctx: DocumentContext) {
        const initialProps = await Document.getInitialProps(ctx);
        return { ...initialProps };
    }
    render() {
        return (
            <Html lang='en-US'>
                <Head>
                    <meta charSet='utf-8' />
                    <link
                        rel='stylesheet'
                        href='https://rsms.me/inter/inter.css'
                    />
                    <link rel='shortcut icon' href='/assets/favicon.ico' />
                    <style
                        type='text/css'
                        dangerouslySetInnerHTML={{ __html: mediaStyles }}
                    />
                    <script
                        async
                        src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
                    />
                    <script
                        dangerouslySetInnerHTML={{
                            __html: `
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());

        gtag('config', '${GA_TRACKING_ID}', {
            page_path: window.location.pathname,
        });
    `
                        }}
                    />
                </Head>
                <body className='loading'>
                    <Main />
                    <NextScript />
                </body>
            </Html>
        );
    }
}

export default MyDocument;

For the sake of thoroughness, here is my package.json

{
    "name": "clean-fade",
    "version": "1.0.0",
    "main": "index.js",
    "repository": "git@gitlab.com:windy-city-devs-llc/clean-fade.git",
    "author": "Andrew Ross <andrew.simpson.ross@gmail.com>",
    "license": "MIT",
    "scripts": {
        "test": "jest",
        "test-all": "yarn lint && yarn type-check && yarn test",
        "codegen": "graphql-codegen --config codegen.yml -r dotenv/config",
        "dev": "next -p 5005",
        "prod:build": "yarn codegen && yarn build",
        "dev:debug": "cross-env NODE_OPTIONS='--inspect' next dev",
        "build": "next build",
        "analyze": "cross-env ANALYZE=true yarn build",
        "find:unused": "next-unused",
        "tsconfig:effective": "tsc --showConfig",
        "pretty:quick": "yarn pretty-quick --staged",
        "prepare": "husky install",
        "lint": "eslint . --ext ts --ext tsx --ext js",
        "format": "prettier --write .",
        "type-check": "tsc --pretty --noEmit"
    },
    "lint-staged": {
        "*.@(ts|tsx)": [
            "yarn lint",
            "yarn format"
        ]
    },
    "next-unused": {
        "alias": {
            "@/components/*": [
                "components/*"
            ],
            "@/config/*": [
                "config/*"
            ],
            "@/graphql/*": [
                "graphql/*"
            ],
            "@/lib/*": [
                "lib/*"
            ],
            "@/pages/*": [
                "pages/*"
            ],
            "@/scripts/*": [
                "scripts/*"
            ],
            "@/styles/*": [
                "styles/*"
            ],
            "@/test/*": [
                "test/*"
            ],
            "@/types/*": [
                "types/*"
            ]
        },
        "debug": true,
        "include": [
            "components",
            "lib",
            "pages"
        ],
        "exclude": [],
        "entrypoints": [
            "pages"
        ]
    },
    "dependencies": {
        "@apollo/client": "^3.3.12",
        "@artsy/fresnel": "^1.3.1",
        "@headlessui/react": "^0.3.1",
        "@reach/portal": "^0.13.2",
        "body-scroll-lock": "^3.1.5",
        "classnames": "^2.2.6",
        "date-fns": "^2.19.0",
        "graphql": "^15.5.0",
        "html-react-parser": "^1.2.4",
        "isomorphic-unfetch": "^3.1.0",
        "js-cookie": "^2.2.1",
        "keen-slider": "^5.4.0",
        "lodash.random": "^3.2.0",
        "lodash.throttle": "^4.1.1",
        "next": "^10.0.9",
        "next-seo": "^4.20.0",
        "next-themes": "^0.0.12",
        "preact": "^10.5.13",
        "react": "^17.0.1",
        "react-dom": "^17.0.1",
        "react-intersection-observer": "^8.31.0",
        "react-markdown": "^5.0.3",
        "react-merge-refs": "^1.1.0",
        "react-tsparticles": "^1.20.1",
        "remark-gfm": "^1.0.0",
        "swr": "^0.5.3",
        "tabbable": "^5.1.6"
    },
    "devDependencies": {
        "@babel/core": "^7.13.10",
        "@graphql-codegen/cli": "^1.21.3",
        "@graphql-codegen/import-types-preset": "^1.18.1",
        "@graphql-codegen/introspection": "^1.18.1",
        "@graphql-codegen/near-operation-file-preset": "^1.17.13",
        "@graphql-codegen/schema-ast": "^1.18.1",
        "@graphql-codegen/typescript": "^1.21.1",
        "@graphql-codegen/typescript-operations": "^1.17.15",
        "@graphql-codegen/typescript-react-apollo": "2.1.1",
        "@next/bundle-analyzer": "^10.0.9",
        "@tailwindcss/aspect-ratio": "^0.2.0",
        "@tailwindcss/forms": "^0.2.1",
        "@tailwindcss/typography": "^0.4.0",
        "@testing-library/dom": "^7.30.0",
        "@testing-library/jest-dom": "^5.11.9",
        "@testing-library/react": "^11.2.5",
        "@testing-library/user-event": "^12.8.3",
        "@types/body-scroll-lock": "^2.6.1",
        "@types/classnames": "^2.2.11",
        "@types/gtag.js": "^0.0.4",
        "@types/jest": "^26.0.20",
        "@types/js-cookie": "^2.2.6",
        "@types/lodash.random": "^3.2.6",
        "@types/lodash.throttle": "^4.1.6",
        "@types/node": "^14.14.35",
        "@types/react": "^17.0.3",
        "@types/react-dom": "^17.0.2",
        "@types/react-test-renderer": "^17.0.1",
        "@types/tabbable": "^3.1.0",
        "@typescript-eslint/eslint-plugin": "^4.18.0",
        "@typescript-eslint/parser": "^4.18.0",
        "autoprefixer": "^10.2.5",
        "babel-jest": "^26.6.3",
        "cross-env": "^7.0.3",
        "dotenv": "^8.2.0",
        "dotenv-cli": "^4.0.0",
        "eslint": "^7.22.0",
        "eslint-config-prettier": "^8.1.0",
        "eslint-plugin-react": "^7.22.0",
        "globby": "^11.0.2",
        "husky": "^5.1.3",
        "identity-obj-proxy": "^3.0.0",
        "jest": "^26.6.3",
        "jest-watch-typeahead": "^0.6.1",
        "lint-staged": "^10.5.4",
        "next-page-tester": "^0.24.0",
        "next-unused": "^0.0.3",
        "postcss": "^8.2.8",
        "postcss-flexbugs-fixes": "^5.0.2",
        "postcss-import": "^14.0.0",
        "postcss-preset-env": "^6.7.0",
        "postinstall-postinstall": "^2.1.0",
        "prettier": "^2.2.1",
        "pretty-quick": "^3.1.0",
        "react-test-renderer": "^17.0.1",
        "stylelint": "^13.12.0",
        "stylelint-config-recommended": "^4.0.0",
        "tailwindcss": "^2.0.3",
        "tailwindcss-line-clamp": "^1.0.5",
        "ts-jest": "^26.5.3",
        "typescript": "^4.2.3",
        "yaml-loader": "^0.6.0"
    }
}

Next.js, How to setup tailwindcss with purge css to remove unused css

  1. install tailwindcss
yarn add -D tailwindcss
npx tailwindcss init
  1. configure your tailwind.config.js
module.exports = {
    content: [
        './pages/**/*.{js,jsx,ts,tsx}',
        './components/**/*.{js,jsx,ts,tsx}'
    ],
    theme: {
        extend: {},
    },
    plugins: [],
}
  1. install purge css plugins
yarn add @fullhuman/postcss-purgecss postcss-preset-env
  1. configure postcss.config.js
module.exports = {
    plugins: {
        tailwindcss: {},
        autoprefixer: {},
        '@fullhuman/postcss-purgecss': {
            content: [
                './pages/**/*.{js,jsx,ts,tsx}',
                './components/**/*.{js,jsx,ts,tsx}'
            ],
            defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
        },
        'postcss-preset-env': {}
    },
}

After that try adding a classname that you intentionally won't use and try to build for production and test from coverage tab that this classname is not loaded within source file

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM