6

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: enter image description here

After: enter image description here

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.

Jonathan Lauwers
  • 255
  • 2
  • 3
  • 8

3 Answers3

13

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.

Daryn
  • 3,394
  • 5
  • 30
  • 41
4

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"
    }
}

Andrew Ross
  • 1,094
  • 7
  • 16
  • 1
    Thank you so much for your very elaborate answer. I have tried both of your solutions without any success, unfortunately. I even downgrades my tailwindcss from 2.0.4 to 2.0.3 and changed the purge option in the tailwind.config.js file but still nothing. This is so strange as I simply followed the tutorial on the tailwind website. If there is anything else that you think might cause this issue, feel free to let me know! I already really appreciate the effort. – Jonathan Lauwers Mar 25 '21 at 21:55
  • Hey Jonathan, Did you try removing the purge css from the postcss.config.js file as well? I think that's the primary culprit. If that doesn't work I'd be more than happy to take a look at the repo if possible. Cheers - Andrew – Andrew Ross Mar 26 '21 at 07:07
  • Yes i took over your postcss.config.js file just to see if that would work but it didn't. Can I contact you on LinkedIn or over mail? Then I can give you access to the repo. Thank you so much for your help. – Jonathan Lauwers Mar 26 '21 at 07:51
  • Sure, here's my linkedin profile link https://www.linkedin.com/in/asross/ – Andrew Ross Mar 27 '21 at 09:56
  • hey did u guys end up figuring this out ? am having the same issue ? but for me if i remove `purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],` from .config file works fine on prod build – Igor Pavlenko Nov 17 '21 at 16:34
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

enter image description here

Louay Al-osh
  • 3,177
  • 15
  • 28