Tari Ibaba is a software developer with years of experience building websites and apps. He has written extensively on a wide range of programming topics and has created dozens of apps and open-source libraries.
Yes, it’s no longer the recommended tool for starting React apps. Some of us were surprised, but many of us knew it was a long time coming.
There are much faster and leaner alternatives available today.
Why shouldn’t you use Create React App anymore?
Create React App was great.
Setting up React apps become as easy as ever without any need to bother with tedious Webpack and Babel configs. But despite the convenience, it had notable issues we just couldn’t ignore:
1. Bulky and slow
Create React App is a bloated tool.
It installs an unbelievable number of packages and takes forever to finish. If you’re a long-time CRA user like me, you’ve probably gotten used to this process, but it’s far from normal.
Once you enter the command to start a new project, you will quickly realize the insane number of packages CRA needs to set up.
Look at how large the node_modules folder is for CRA compared to Vite, a superior alternative. And both folders were just newly set up.
Many packages mean longer install times, even if you’re using PNPM or Yarn 2.
2. Insecure and outdated packages
Bundling so many packages makes it harder to keep track of them all, and many become abandoned and insecure later.
Things improved but any insecure package is something to worry about, and there are still quite a few.
In my GitHub repos using CRA, I get several Dependabot alerts at every turn – even from one of my first GitHub projects.
And they’re almost always about security flaws in packages CRA installed.
3. Limited customization options
Create React App is a black box.
To add new libraries and customize it you usually have to depend on libraries like Craco – heck, before CRA version 5, Craco was pretty much our only way to get Tailwind CSS support.
I remember my earlier days of creating various Electron + React desktop apps, where I had to install react-app-rewired just to set the target to 'electron-renderer' to access Node modules in the React part of the app.
For total control, you can always do npm eject, but of course, then you become responsible for all the dependencies installed by CRA, which has trade-offs that may not be worth it for you.
What to use instead of Create React App
Old habits die hard, sure.
If you’ve been hesitating to switch to a superior tool, hopefully, the React team dumping CRA should finally do it for you. There are far more modern and better-maintained alternatives out there; let’s check out some of the most popular ones:
It’s a zero-config bundler with no setup required, yet it’s still more flexible than Create React App.
Parcel supports all the latest web technologies, including React, Vue, and TypeScript, and it can bundle any type of asset, from images and CSS to HTML and JavaScript.
Plus, it has excellent performance, thanks to its use of worker threads, which allows it to parallelize tasks and speed up builds.
Parcel also has HMR support and is updated often with the latest features and optimizations like Vite. You can trust the team to keep up with the latest web technologies and best practices.
And what’s the result: your projects are built with the best tools available.
Webpack is still king if you know what you’re doing.
The level of control of flexibility it gives you is unmatched as there are a massive number of options you can set, far more than just path and filename.
Another benefit to note: Webpack can generate a development build without a local server, unlike Parcel and others.
This particular feature saved me when I was debugging React in a VSCode extension. Loading data from localhost didn’t work properly for a Webview; I had to build to a local file and read the contents.
With source maps enabled and dev mode turned on, I could easily see the exact line where the error occurred in the TS code. Unlike with Parcel, where all I got was a minified build.
Of course, setting up a Webpack config can be complex and more involved – which is why CRA came around in the first place. You must configure various options to get everything working perfectly.
But once you’re set-up, you’ll have a heavily personalized build process for your project. It’s a trade-off that may or may not be worth it for you.
Frameworks to replace Create React App
Need React’s state and UI capabilities without extras? The tools we’ve seen are great.
Want high-performance apps with advanced features like SSR? Then consider frameworks with built-in routing, data fetching, etc.
Another SSR-enable framework with powerful data features like:
Intelligent network error handling
Parallel fetching of CSS, JavaScript, and other assets to save time
Automatically ensuring the UI data is in sync with the server data after a user action.
One stand-out Remix feature is nested routes, which makes it possible to associate a specific section of a page with a particular route segment so that only that section updates when the route segment changes. This accelerates page transitions as no time is wasted re-rendering the entire page.
Gatsby is another awesome one – for scalable & fast-loading static sites.
There are a variety of starter kits and plugins to easily extend your site’s functionality and build a blog, e-commerce site, or portfolio site in no time at all.
With Gatsby, it’s effortless to power up your app with data from a CMS, especially with the GraphQL data layer for rapid integration with various APIs and services.
Final thoughts
CRA was a popular tool.
But it had its issues. It was bulky, outdated, and had limited customization.
Adaptation is crucial in the dynamic landscape of web development. By embracing modern tools, such as Vite, and Parcel, and in-depth frameworks like Next.js, Remix, and Gatsby, we can build faster and more efficient React apps.
The “Cannot read property ‘classList’ of null” error happens in JavaScript when you try to access the classList property on an element that isn’t in the HTML DOM.
Let’s look at various ways to quickly fix this error.
Fix: Ensure correct selector
To fix the “Cannot read property ‘classList’ of null” error in JavaScript, ensure the correct selector accesses an existing HTML element.
HTMLCopied!
<div>Welcome to Coding Beauty</div>
<button class="btn-1">Be amazing</button>
Check for any mistakes in the selector symbols in the script. Check for any mistakes in the ID or class name in the HTML tag. Maybe you forgot to set that id or class attribute at all?
JavaScriptCopied!
// forgot the '.' symbol used for class selectors
const button = document.querySelector('btn-1');
console.log(button); // 👉️ undefined
// ❌ Uncaught TypeError: Cannot read properties of undefined (reading 'classList')
button.classList.add('active');
Fix: Ensure DOM load before .classList access
The “Cannot read property ‘classList’ of undefined” error also occurs when you try to access .classList on an element that the browser hasn’t added to the DOM yet.
Maybe because your <script> is in the <head> tag and executes before the element’s parsing:
HTMLCopied!
<!DOCTYPE html>
<html lang="en">
<head>
<title>Coding Beauty Tutorial</title>
<!-- ❌ Script is run before button is declared -->
<script src="index.js"></script>
</head>
<body>
<div id="element">
console.log('Easy answers to your coding questions and more...');
</div>
</body>
</html>
The script tag is placed in the <head> tag above where the div is declared, so index.js can’t access the div.
index.jsCopied!
const element = document.querySelector('.element');
console.log(element); // 👉️ undefined
// ❌ Uncaught TypeError: Cannot read properties of undefined (reading 'classList')
element.classList.add('highlight');
Solution: Move script to bottom
To fix the error in this case, move the script tag to the bottom of the body, after all the HTML elements have been declared.
HTMLCopied!
<!DOCTYPE html>
<html lang="en">
<head>
<title>Coding Beauty Tutorial</title>
</head>
<body>
<div id="element">
console.log('Easy answers to your coding questions and more...');
</div>
<!-- ❌ Script is run after element is added to the DOM -->
<script src="index.js"></script>
</body>
</html>
Now index.js will have access to the div and all the other HTML elements, because the browser would have rendered them by the time the script runs:
index.jsCopied!
const element = document.querySelector('.element');
console.log(element); // 👉️ undefined
// ✅ Works as expected
element.classList.add('highlight');
Solution: Access .classList in DOMContentLoaded event listener
Another way to fix the “cannot read property ‘addEventListener’ of null” error is to add a DOMContentLoaded event listener to the document and access the element in this listener.
HTMLCopied!
<!DOCTYPE html>
<html lang="en">
<head>
<title>Coding Beauty Tutorial</title>
<!-- Script placed above accessed element -->
<script src="index.js"></script>
</head>
<body>
<div id="element">
console.log('Coding is more than a means to an end...');
</div>
</body>
</html>
The DOMContentLoaded event fires when the browser fully parses the HTML, whether or not external resources like images and stylesheets have loaded.
So regardless of where we place the script, the code in the listener only runs after every element is active in the DOM.
index.jsCopied!
const element = document.querySelector('.element');
console.log(element); // 👉️ undefined
// ✅ Works as expected
element.classList.add('highlight');
Adding a favicon to a website enhances usability and branding. It helps identify the website in browser tabs and bookmarks, improving user recognition and trust.
Let’s learn how to quickly add a favicon image to a Next.js app
Add favicon automatically in Next.js 13 App Router
To add a favicon in Next.js 13 App Router, add a favicon.ico file to the app/ directory. Next.js will automatically detect favicon.ico and display it on the page.
Here we’ve added the favicon.ico to our VS Code Next.js project.
And this is all we need to do – here’s layout.tsx:
And we’ll instantly see the image next to the page title in the browser tab:
Apart from favicon and .ico, Next.js also auto-discovers the following file name and extension combinations in the app/ directory
icon with .ico, .jpg, .jpeg, .png, or.svg extension.
app-icon with .jpg, .jpeg, .png extension.
You can rename PNGs to .ico files and they will still work.
Add favicon automatically in Next.js Pages Router
To add a favicon automatically in the Next.js pages directory, place a favicon.ico file in your public/ directory, and your browser will automatically detect the file for the site icon.
If your image file isn’t a .ico, you’ll need to either rename it to favicon.ico, or manually specify the filename.
Set Next.js favicon statically in Next.js 13 App Router
To add a favicon to a Next.js 13 app, you can also export a Metadata object with an icons property in your layout.tsx file:
The image file should be in your public directory.
Set Next.js favicon with HTML in Pages Router
To add a site icon in the Next.js pages or app directory, use the HTML <link> tag in your _document.tsx or _document.js file, just like in vanilla HTML:
To add a class to the HTML body element in JavaScript on page load, call the classList.add() method on it, i.e., document.body.classList.add(element).
HTMLCopied!
<!DOCTYPE html>
<html>
<head>
<title>Coding Beauty Tutorial</title>
</head>
<body class="dev coding">
<div>This is a div element.</div>
</body>
</html>
JavaScriptCopied!
document.body.classList.add('class-3');
The body property is the HTMLElement object that represents the body tag in the markup.
The classList property is a DOMTokenList object that represents the list of classes an element has.
The add() method of the classList property takes a list of classes and adds them to an element.
JavaScriptCopied!
<!DOCTYPE html>
<html>
<head>
<title>Coding Beauty Tutorial</title>
</head>
<body class="dev coding beauty">
<div>This is a div element.</div>
</body>
</html>
You can pass multiple arguments to add() to add more than one class to the body. For example, we can add both beauty and magic to the body in single statement.
JavaScriptCopied!
document.body.classList.add('beauty', 'magic');
To produce this HTML markup:
JavaScriptCopied!
<!DOCTYPE html>
<html>
<head>
<title>Coding Beauty Tutorial</title>
</head>
<body class="dev coding beauty magic">
<div>This is a div element.</div>
</body>
</html>
If you add class that already exists on the HTML body, add() ignores the class instead of throwing an exception.
Add class to body tag in <head> tag
To add a class to the body element in the <head> tag using JavaScript, use the DOMContentLoaded event and the document.body.classList.add() method.
For example:
JavaScriptCopied!
<!DOCTYPE html>
<html>
<head>
<title>Coding Beauty Tutorial</title>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.body.classList.add('beauty', 'magic');
});
</script>
</head>
<body class="dev coding">
<div>This is a div element.</div>
<script src="index"></script>
</body>
</html>
The DOMContentLoaded event runs when the HTML is completely parse and the DOM has loaded.
The <script> loads and the JavaScript runs before the browser renders the HTML, so without DOMContentLoaded, document.body will be null in the <script>, causing the “Cannot read property ‘classList’ of undefined” JavaScript error.
Add class to body tag on click
If you’d like to add a class to the body tag when the user clicks an element, set a click listener on element and call document.body.classList.add() in this listener.
For example:
HTMLCopied!
<!DOCTYPE html>
<html>
<head>
<title>Coding Beauty Tutorial</title>
</head>
<body class="dev coding">
<div>So you can code</div>
<button class="amazify">Be amazing</button>
</body>
</html>
Toggling a class on the body element in JavaScript simplifies code implementation by handling the addition and removal of the class in a single line of code.
And this single line of code is a call to the body’s classList.toggle() method:
Setting the page title and meta description helps improve search engine visibility, increase click-through rates, and provide concise summaries of webpage content.
In this article, we’re going to learn how we easily set the page title and meta description in a Next.js project.
Set static page title and meta description in Next.js App Router
To set page title and meta description statically in Next.js 13’s app directory, create a Metadata object in a page.tsx file and export it:
src/app/page.tsxCopied!
import React from 'react';
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Coding Beauty',
description:
'codingbeautydev.com: Coding - the art, the science, and the passion.',
};
export default function Page() {
return (
<main>
<h1>Welcome to Coding Beauty</h1>
</main>
);
}
The tab will have this title, and the page will have a meta tag containing this description:
We can also do this in a layout.tsx to make every page using this layout have this title and meta description by default – if the page doesn’t have its own.
src/app/layout.tsxCopied!
import { Metadata } from 'next';
import '@/styles/globals.css';
export const metadata: Metadata = {
title: 'Coding Beauty',
description: 'The official Coding Beauty home page.',
icons: {
icon: '/favicon.png',
},
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Set title and meta description dynamically in Next.js App Router
You can use the generateMetadata() function to set the page title and meta description using dynamic information we don’t know beforehand, like data from an API.
TypeScriptCopied!
export function generateMetadata({ params }: Props): Promise<Metadata> {
// fetch data with `params`
// return a `Metadata` object
return {
title: post.name,
description: post.description,
}
}
Let’s look at a full example, where we fetch API data for a post in our hypothetical social media app, and we use generateMetadata() to set the page title and meta description for the page based on what we get from the endpoint URL.
src/pages/posts/[id]/page.tsxCopied!
import { Metadata } from 'next';
type Props = {
params: { id: string };
};
export async function generateMetadata({
params,
}: Props): Promise<Metadata> {
const id = params.id;
const url = `https://api.mysocialapp.com/posts/${id}`;
const post = await fetch(url).then((res) => res.json());
return {
title: post.title,
description: post.description,
};
}
export default async function Page({ params }: Props) {
const { id } = params;
const url = `https://api.mysocialapp.com/posts/${id}`;
// fetch again!
// But don't worry, Next.js caches the `fetch()` calls
const post = await fetch(url).then((res) => res.json());
return (
<>
<h1>{post.title}</h1>
<div>{post.content}</div>
</>
);
}
We fetch data from the same endpoint twice, one for the title and meta description, and another to display the title and other information to the actual users on the page.
Next.js caches the results of the fetch in generateMetadata() for use in Page to prevent multiple requests and avoid performance hits.
If you export a Metadata object and also have generateMetadata() function, Next.js will use generateMetadata()‘s return value.
Set page title and meta description in Next.js Pages Router (<= 12)
To set the page title and meta description in the Next.js pages directory, create a Head component, and place meta and title tags in it, like you would in vanilla HTML:
pages/index.tsxCopied!
import Head from 'next/head';
export default function Home() {
return (
<>
<Head>
<title>Coding Beauty</title>
<meta
name="description"
content="A brand all about coding passion and success"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.png" />
</Head>
<main>
<h1>Welcome to Coding Beauty</h1>
</main>
</>
);
}
We can also set the page title and meta description in your _app.tsx or _app.jsx file to make each page without a title or description use this as a default.
pages/_app.tsxCopied!
import '@/styles/globals.css';
import type { AppProps } from 'next/app';
import Head from 'next/head';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<title>Coding Beauty</title>
<meta
name="description"
content="A page on the Coding Beauty website"
/>
</Head>
<Component {...pageProps} />
</>
);
}
Set page title and meta description dynamically in Next.js Pages Router (<= 12)
You can also set the page title and meta description in the pages directory based on some data not known ahead of time.
For instance, to set the meta information based on API data, you’ll fetch the data in getServerSideProps and display it in the title and meta tags that are in the Head tag.
With this, you’ll have a type that only accepts strings matching the key name, and your code editor’s intellisense should indicate:
Create type from another type’s keys in TypeScript
We use typeof because brand is an instance object, not a type. If it was a type, we would omit typeof:
TypeScriptCopied!
type Brand = {
name: string;
domain: string;
color: string;
};
// 👇 type Keys = 'name' | 'domain' | 'color';
type BrandKeys = keyof Brand;
As before, you’ll have a type that only contains strings matching the key name, and your code editor should detect this:
Create type from object values in TypeScript
We can use typeof and type indexing to easily create a type from an object’s values in TypeScript:
TypeScriptCopied!
const site = {
url: 'codingbeautydev.com',
color: 'blue',
topic: 'coding',
} as const; // 👈 const assertion
// 👇 type Values = 'codingbeautydev.com' | 'color' | 'coding'
type Values = (typeof site)[keyof typeof site];
We this, we’ll have a type that only accepts strings matching the values of the object:
Otherwise, there’ll be a TypeScript error
Create generic type from object values in TypeScript
The as const is called a const assertion in TypeScript. It tells the compiler to infer the most specific type possible from an expression. Without it, the inferred value type will be a primitive, like string or number or union of these primitives – string | number for example.
TypeScriptCopied!
const site = {
url: 'codingbeautydev.com',
color: 'blue',
topic: 'coding',
};
// 👇 type Values = 'string'
type Values = (typeof site)[keyof typeof site];
// 👇 No error - `Values` takes any string
const testObj: Values = 'random string';
Values is no different from the string type here, and VS Code confirms it:
If there are multiple primitive types, the resulting generic type will be a union of all those primitives:
TypeScriptCopied!
const site = {
url: 'codingbeautydev.com',
age: 1000, // 👈 also `number` type now
topic: 'coding',
};
// 👇 type Values = 'string' | number
type Values = (typeof site)[keyof typeof site];
// 👇 No error
const testObj: Values = 34124;
Create generic type from another type’s values in TypeScript
You can also create generic types from another type’s values in TypeScript, like this:
TypeScriptCopied!
type Site = {
url: 'codingbeautydev.com';
age: number;
topic: string;
};
// 👇 type Site = 'string' | 'number'
type Values = Site[keyof Site];
To allow Next.js to display images from all domains, modify your next.config.js file to add an object with protocol and hostname properties to the remotePatterns array property.
Or you can modify your next.config.js to include codingbeautydev.com:
Of course, if you don’t have any domains property, you just create one with the new domain as its single element.
While the image.domains is simple and straightforward, the remotePatterns property gives us more flexibility with its support for wildcard pattern matching, and protocol, port, and pathname restriction.
These advanced restrictions remotePatterns provide greater security when you don’t own all the content in the domain, which is why the Next.js team recommends it.
Why do we need to be explicit with domains for Next.js images?
So whether you use remotePatterns or images.domains, you still need to be explicit about the domains that a next/image component can display.
By doing so, you can prevent the potential security risks of loading images from random, unverified URLs that could contain malicious code.
The “This project is configured to use Yarn” error happens in pnpm install when you have a field in your package.json file that specifies Yarn as the package manager.
The package.json field may have come about when you ran a yarn set version ... command, for example, yarn set version stable to set up Yarn Berry, or yarn set version berry to migrate from Yarn v1 to Yarn Berry.
Fix “This project is configured to use Yarn” error
To fix the this pnpm install usage error, simply remove the "packageManager" field from package.json: