PurgeCSS does not remove unused CSS from NextJS project - next.js

I'm trying to remove unused css from my NextJS project using PurgeCSS. However, I'm having difficulty getting the most basic integration of PurgeCSS into my project to work.
I'm using this documentation: https://www.purgecss.com/guides/next.
My next.config file looks like this:
// next.config.js
const withCss = require('#zeit/next-css')
const withPurgeCss = require('next-purgecss')
module.exports = withCss(withPurgeCss())
Here is my component:
import React from 'react'
import App from 'next/app'
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props
return (
<>
<style jsx global>{`
.purgecss-test {
color: red;
}
`}</style>
<Component {...pageProps} />
</>
)
}
}
When I do a global search in my code base for 'purgecss-test', I only get the one result that's in the component above, so I am expecting that style to be removed during the build. However, when my app builds and I navigate to the page and inspect the source, I can still see it there:
<style amp-custom="">.purgecss-test{color:red;}

Try customizing PostCSS instead as per this page (ignore tailwindcss): https://nextjs.org/learn/basics/assets-metadata-css/styling-tips
A bit simplified, install #fullhuman/postcss-purgecss and postcss-preset-env, then create a postcss.config.js file in the top-level directory and enter this in it:
module.exports = {
plugins: [
[
'#fullhuman/postcss-purgecss',
{
content: [
'./pages/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}'
],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
}
],
'postcss-preset-env'
]
};

Related

How do I import a 2nd CSS framework (prefixed) into my Vite-based Vue 3 project (1st framework being TailwindCSS)

So I have a default Laravel app working with Vue 3 and assets configured by Vite, TailwindCSS and TailwindUI installed. This all works fine.
I understand to have another CSS framework, I need to prefix it to avoid clashes. According to these instructions, I need to add the following line: (after installing via npm):
import PrimeVue from 'primevue/config'; //I have included this in app.js
as well as reference these styles:
primevue/resources/themes/saga-blue/theme.css
primevue/resources/primevue.min.css
primeicons/primeicons.css
How exactly do I reference these css files with a prefix so as to avoid clashes?
My postcss.config.js file currently looks like this:
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
My app.js looks like this:
import './bootstrap';
import '../css/app.css';
import { createApp, h } from 'vue';
import { createInertiaApp } from '#inertiajs/inertia-vue3';
import { InertiaProgress } from '#inertiajs/progress';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
/* added by me*/ import PrimeVue from 'primevue/config';
/* added by me*/ import InputMask from 'primevue/inputmask';
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({ el, app, props, plugin }) {
return createApp({ render: () => h(app, props) })
.use(plugin)
.use(ZiggyVue, Ziggy)
.use(PrimeVue)
/*added by me*/ .component('InputMask', InputMask)
.mount(el);
},
});
InertiaProgress.init({ color: '#4B5563' });

Why isn't my t() texts refreshing in localhost/en but refreshing in localhost/fr on i18n.changeLanguage()?

Hi
I just made a website with a darkmode and multilanguage support to test around but I ran into an issue.
the code
I got rid of all things that aren't an issue
portfolio/src/pages/index.tsx
import { useTranslation } from 'react-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
export default () => {
const { t,i18n } = useTranslation('common')
return <div onClick={()=>i18n.changeLanguage(i18n.language=='fr'?'en':'fr')}>
<div>{i18n.language}</div>
<span>{t('debug')}</span>
</div>
}
export async function getStaticProps({ locale }:any) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
// Will be passed to the page component as props
},
};
}
portfolio/src/public/locales/en/common.js
{"debug":"english"}
portfolio/src/public/locales/fr/common.js
{"debug":"français"}
portfolio/next-i18next.config.js
const path = require("path");
module.exports = {
debug: false,
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
localePath: path.resolve('./src/public/locales'),
};
portfolio/src/pages/_app.tsx
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import {appWithTranslation} from 'next-i18next'
export default appWithTranslation(({ Component, pageProps }: AppProps) => {
return <Component {...pageProps} />
})
The issue
When I do npm run dev and go to http://localhost:3000/fr, the page defaults to french and works good I can swap between languages without problems but when i go to http://localhost:3000/en the t('debug') doesn't translate when the i18n.language changes as intended.
Found what I wanted
So basicaly I need to use a next Link that will change the local and the link
Code application
index.js
//...
export default () => {
const { t,i18n } = useTranslation('common')
return (
<div>
<Link
href={i18n.language=='fr'?'/en':'/fr'}
locale={i18n.language=='fr'?'en':'fr'}
>{i18n.language}</Link>
<div>{t('debug')}</div>
</div>
)
}
//...
result
Now the text changes as intended both in the /fr and /en because it switches between the 2 however the result is far from smooth. It reloads the page and i'd like to avoid that because I use some animations on it.
Found what i wanted part 2
Browsing through the next-i18next documentation I found what I wanted.
solution
I needed to load the props using getStaticProps and in the serverSideTranslation function i needed to pass as argument the array off ALL the language necessary to load the page ['en','fr'] because i switched between the 2

#aws-amplify/ui-react how to customize UI if my project uses SASS?

I'm extending a Next.js (React) project that was built by someone else, which uses .scss files (SASS) for styling. This is the project in question https://github.com/codebushi/nextjs-starter-dimension
Now I'm adding an authentication flow to the project using #aws-amplify/ui-react. Everything works fine, but I want to customize the UI style. I've found in the documentation that I can do that through :root in globals.css, as so:
:root {
--amplify-primary-color: #ff6347;
--amplify-primary-tint: #ff7359;
--amplify-primary-shade: #e0573e;
}
Documentation here: https://docs.amplify.aws/ui/customization/theming/q/framework/react
I know pretty much nothing about SASS except the basics. How would I do the equivalent of setting those variables in :root?
Edit with more details
This is my next.config.js:
module.exports = {
webpack: (config, { dev }) => {
config.module.rules.push(
{
test: /\.scss$/,
use: ['raw-loader', 'sass-loader']
}
)
return config
}
}
This is my authentication page where the Amplify elements are defined:
import { useState, useEffect } from 'react'
import { AuthState, onAuthUIStateChange } from '#aws-amplify/ui-components';
import { AmplifyAuthenticator, AmplifyAuthContainer, AmplifySignUp, AmplifySignIn, AmplifySignOut } from '#aws-amplify/ui-react';
import Router from 'next/router'
const Auth = (props) => {
const [authState, setAuthState] = useState();
const [user, setUser] = useState();
useEffect(() => {
return onAuthUIStateChange((nextAuthState, authData) => {
setAuthState(nextAuthState);
setUser(authData);
//console.log(`authData: ${JSON.stringify(authData, null, 2)}`);
});
}, []);
if (authState === AuthState.SignedIn && user) {
Router.push('https://mywebsite')
return <p>Redirecting...</p>
}
return (
<AmplifyAuthContainer>
<AmplifyAuthenticator usernameAlias="email">
<AmplifySignUp
slot="sign-up"
usernameAlias="email"
formFields={[
{
type: "email",
label: "Email Address *",
placeholder: "Enter your email address",
inputProps: { required: true },
},
{
type: "password",
label: "Password *",
placeholder: "Enter your password",
inputProps: { required: true },
},
]}
/>
<AmplifySignIn slot="sign-in" usernameAlias="email" />
</AmplifyAuthenticator>
</AmplifyAuthContainer>
);
}
export default Auth;
As per the answer below from Sean W, I've already tried creating an _app.js with:
import '../styles/global.scss'
const App = ({ Component, pageProps }) => {
return <Component {...pageProps} />
}
export default App;
with global.scss:
:root {
--amplify-primary-color: #ff6347;
--amplify-primary-tint: #ff7359;
--amplify-primary-shade: #e0573e;
}
But the CSS variables don't seem to be replaced.
Include the styles in your pages. The easiest way is to create a new SCSS file and include it in a custom _app.js.
By default, Next supports SASS - you only need to install the sass npm package and likely do not need a custom next.config. This could be one of your problems.
file - global.scss -
:root{
--amplify-primary-color: #ff6347;
--amplify-primary-shade: #e0573e;
}
//scoped & redeclared to an element with id #__next
:root #__next {
--amplify-primary-color: #ff6347;
--amplify-primary-shade: #e0573e;
}
Amplify could also be setting setting styles after you have already defined them for the :root - if this is the case you will need to scope your custom styles to take precedence over the default Amplify CSS variables. To do this - redeclare them in a CSS rule that is more specific than :root like the example above - keeping order of precedence in mind
Amplify also lets you directly target components.
amplify-authenticator {
--amplify-primary-color: #ff6347;
background: var(--amplify-primary-color);
padding: 5px;
}
// scoped & redeclared
:root #__next amplify-sign-in{
--amplify-primary-color: #ff6347;
}
The amplify elements that can be targeted are
amplify-authenticator
amplify-sign-in
amplify-confirm-sign-in
amplify-sign-up
amplify-confirm-sign-up
amplify-forgot-password
amplify-require-new-password
amplify-verify-contact
amplify-totp-setup
import your file - pages/_app.js
import 'path/to/global.scss';
const App = ({ Component, pageProps }) => <Component {...pageProps} />;
export default App;
Global variables (the equivalent of :root variables) can be created simply with this line.
$color = #c0ff33 !important;
The content can be anything that is available in css and partially sass.
For large projects, creating a variables.scss file and including in it all these variable declarations can be truly helpful for changing multiple variables at once. To include this file, just do #import "./variables.scss" at the top of your file.
Hope this helps! Good luck in your sassy endeavors!

Next.js and Styled Components go out of sync between the server and the client on refresh

I have a Next.js app using styled components. On first load of any page, there are no complaints, and everything looks properly styled. When I refresh a page however, everything still looks proper, but I get a console error reading:
Warning: Prop `className` did not match. Server: "sc-TXQaF bfnBGK" Client: "sc-bdnylx kKokSB"
I've tried simplifying the styles on the specific component, and the error persists. I've tried removing the component entirely from the DOM, and that results in the same error on the next element in the DOM. So it seems to be a global issue.
I've followed the guide for using Next.js and Styled Components found here: https://github.com/vercel/next.js/tree/master/examples/with-styled-components
I have the .babelrc file in the root:
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
I have the _document.js file in my pages directory:
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
Here is an example of one of my styled components:
import styled from 'styled-components';
export const Block = styled.div`
margin: ${props => props.small ? '2rem 0' : '4rem 0'};
margin-top: ${props => props.clearTop ? '0' : null};
`;
... although I've tried to dumb it down to something as simple as this with no change in the console error:
import styled from 'styled-components';
export const Block = styled.div`
position: relative;
`;
Finally, here's a dumbed down page that still produces the error:
import { useContext, useEffect } from 'react';
import { useRouter } from 'next/router';
import Layout from '../components/layout';
import { Block } from '../components/styled/Block';
import { userContext } from '../context/userContext';;
function Profile() {
const router = useRouter();
const { loggedIn } = useContext(userContext);
useEffect(() => {
if (!loggedIn) router.push('/login');
}, [loggedIn]);
return (
<Layout>
<Block>
<h1>Test</h1>
</Block>
</Layout>
)
}
export default Profile;
Kind of at my wits end here.
I believe I figured out an answer. I didn't have the dev dependency for babel styled components.
npm install babel-plugin-styled-components --save-dev
Your package.json file should have this:
"devDependencies": {
"babel-plugin-styled-components": "^1.11.1"
}
After this was installed, along with the _document.js and .babelrc files correctly placed in your app, you shouldn't have any problems.
I had this issue for the last 1 month and finally got a solution that worked for me!
So the solution here is to get the styling exclusively from the server.
from the docs:
Basically you need to add a custom pages/_document.js (if you don't
have one). Then copy the logic for styled-components to inject the
server side rendered styles into the <head>
To solve this issue is you need something like this in your Document component:
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
The final step (if the error persists) is to delete the cache: delete the .next folder and restart the server
The full example code from Next documentation is Here
with the Next Complier on the latest version of next, you should only update your next.config file and _document file, and you will be all set. Babel will cause conflict with the NextJS compiler.
Here you can check the files. If you don't use TS, just replace
ctx: DocumentContext
with only
ctx
Example app with styled-components

Generate RTL CSS file in create-react-app and switch between them based on change in state

I'm using create-react-app for a multi-language project.
I want to use some library like "cssJanus" or "rtlcss" to convert the Sass generated CSS file into a separate file and then use that newly generated file when I switch to another language.
Here's how my index.js looks like ...
import React from "react";
import ReactDOM from "react-dom";
import * as serviceWorker from "./serviceWorker";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
import App from "./App";
import { configureStore } from "./store/configureStore";
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();
And here's how my "App.js" looks like ...
import React, { Component } from "react";
import "./App.scss";
import { Route, Switch } from "react-router-dom";
import SignIn from "./features/signin/SignIn";
class App extends Component {
render() {
return (
<>
<Switch>
<Route path="/" exact component={SignIn} />
</Switch>
</>
);
}
}
export default App;
As you can see I'm using "./App.scss" file that simply have a bunch of #import statements to another ".scss" files in the "./src/css/" directory ...
/* autoprefixer grid: on */
#import "css/reset";
#import "css/variables";
#import "css/global";
I need your advice on how to do that. How to convert the generated CSS from App.scss to RTL into their own .css file and switch between them and the original generated CSS based on a change in the global state.
I searched a lot for something like this but with no luck.
Or if you have a better approach I'm all ears.
Here is a simple solution that requires ejecting and adding a lightweight webpack-rtl-plugin.
After running
npx create-react-app react-rtl
cd react-rtl
yarn eject
yarn add -D webpack-rtl-plugin #babel/plugin-transform-react-jsx-source
Go to config/webpack.config.js and make some tweaks:
// import the plugin
const WebpackRTLPlugin = require('webpack-rtl-plugin')
// ...
module: { ... }
plugins: [
// ...,
// use the plugin
new WebpackRTLPlugin({ diffOnly: true })
].filter(Boolean),
// ...
On this stage, if you run yarn build and look up build/static/css folder, you should hopefully see additional .rtl.css file that contains your rtl styles.
Then we need to tell webpack to use MiniCssExtractPlugin.loader for development as well so it will serve styles through link tags instead of inline styles:
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && { loader: MiniCssExtractPlugin.loader }, // <-- use this
// isEnvDevelopment && require.resolve('style-loader'), <-- instead of this
and don't forget the plugin, lol:
module: { ... }
plugins: [
// ...,
// isEnvProduction && <-- comment this out
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
// ...
].filter(Boolean),
And from here you can finally grab your default stylesheet href and use to insert rtl styles. Here's how you could implement it:
class RtlCssBundleService {
constructor() {
this.rtlApplied = false
this.rtlStyles = [];
this.ltrStyles = Array.from(
document.querySelectorAll('link[rel="stylesheet"]')
)
}
insert = () => {
if (this.rtlApplied) { return }
this.rtlApplied = true
if (this.rtlStyles.length) {
return this.rtlStyles.forEach(style => {
document.body.appendChild(style)
})
}
this.rtlStyles = this.ltrStyles.map(styleSheet => {
const link = document.createElement("link")
link.href = styleSheet.href.replace(/\.css$/, '.rtl.css')
link.rel = "stylesheet"
document.body.appendChild(link)
return link
})
}
detach = () => {
this.rtlApplied = false
this.rtlStyles.forEach(style => {
document.body.removeChild(style)
})
}
toggle = () => {
return this.rtlApplied
? this.detach()
: this.insert()
}
}
const rtlStyles = new RtlCssBundleService()
export default rtlStyles
Then use this from any of your components.
So anyway, I'm sure I've missed something and maybe that is a terrible approach, but it seems to work and here is the demo
If you use flexbox and css grid they have RTL support built in. Then use CSS Logical Properties for margin, padding, border, etc. If that is not enough, then you can use [dir="rtl"] .your-class as a fallback.
Now you don't have two separate css files to maintain.
Here is a cross browser margin-right example.
-webkit-margin-end: 25px;
margin-inline-end: 25px;
#supports (not (-webkit-margin-end: 0)) and (not (margin-inline-end: 0)) {
margin-right: 25px;
}
You could wrap that up into a mixin for easier use across your app.
Looking around there is a library called react-with-direction from airbnb that provides a DirectionProvider - component you could wrap your components in based on the language. Hope that helps.

Resources