>

Blogs

A person is sketching on a tablet.

The Millisecond Mindset: A Front-End Performance Checklist

In front-end development, "fast" is a relative term. But to a user, it's binary. An interface is either instant, or it's "laggy." The line between these two perceptions is not measured in seconds, but in milliseconds. This is the Millisecond Mindset: a development philosophy where every line of code, every asset, and every network request is weighed against its impact on the user's perception of speed.

Human-computer interaction studies, famously by the Nielsen Norman Group, give us a clear framework:

  • 100ms (0.1 seconds): The limit for an action to feel "instant." The user feels like they are directly manipulating the UI.

  • 1 second: The limit for a user's flow of thought to remain uninterrupted. If they have to wait longer, their attention begins to drift.

  • 10 seconds: The absolute limit for keeping a user's attention. Anything longer, and the user is likely to abandon the task or the site entirely.

In an era of 5G and fiber optics, it's tempting to think performance is a solved problem. It's not. Mobile networks are unreliable, devices have varying processing power, and our applications are more complex than ever. Adopting the Millisecond Mindset means shifting from "does it work?" to "does it feel instant?" It’s the difference between a usable product and a delightful one. It directly impacts conversion rates, bounce rates, and user satisfaction.

Here is a performance checklist to help you get there:


1. Asset Optimization (The "Weight")


Every byte counts. The less you send over the wire, the faster the page will load, especially on mobile networks.

  • Images: This is often the biggest offender. Use modern formats like WebP or AVIF, which offer superior compression and quality compared to JPEG or PNG. Use responsive images (<picture> tag or srcset) to serve different sizes for different screens. Never use a 2000px image for a 200px thumbnail.

  • Minification: Minify your CSS and JavaScript. This process removes all whitespace, comments, and shortens variable names, shrinking the file size without changing the logic.

  • Compression: Ensure your server is using Gzip or, even better, Brotli compression to compress assets before sending them.

  • Fonts: Web fonts can be heavy. Use formats like WOFF2 and consider font subsetting—a technique where you only include the characters you actually need (e.g., just Latin characters) instead of the entire font file.


2. Network & Caching (The "Requests")


A site can be slow not because its files are big, but because it has to make too many trips to get them.

  • Reduce Requests: While HTTP/2 and HTTP/3 have made "request bundling" less critical, it's still a good practice. Limit third-party scripts (analytics, ads, trackers) as each one adds a new domain lookup and download.

  • Leverage a CDN: A Content Delivery Network (CDN) hosts your static assets (images, CSS, JS) on servers all over the world. This means a user in Tokyo downloads assets from a server in Asia, not from your main server in Virginia, drastically reducing latency.

  • Master Caching: Use the Cache-Control HTTP header to tell the browser how long it can "remember" a file. Your logo, CSS, and fonts probably don't change for weeks. Caching them means a return visitor loads the page almost instantly.

  • Service Workers: For the ultimate in performance, a Service Worker can cache your entire app "shell," allowing your site to load offline or near-instantly on repeat visits (a core part of a Progressive Web App, or PWA).


3. Render Path Optimization (The "Sequence")


How the browser builds your page is just as important as how fast it gets the files.

  • The Critical Path: Prioritize loading only what the user needs to see "above the fold" (the visible part of the screen).

  • Critical CSS: This technique involves inlining the absolute minimum CSS needed to style the above-the-fold content directly in the <head> of your HTML. This allows the browser to start painting the page immediately, while the rest of your styles.css loads non-blockingly.

  • Script Loading: Never just drop a <script> tag in your <head> unless you have a very good reason. Use the defer attribute to download the script while the HTML is parsing and execute it after the document is built. Use async for non-essential third-party scripts that can run whenever they're ready.

  • Code Splitting: Don't ship your entire JavaScript application in one giant app.js file. Use your bundler (like Webpack or Vite) to split your code by route or component. This way, a user visiting your homepage only downloads the JavaScript for the homepage.

Performance isn't a feature you add at the end. It's a fundamental part of the design and engineering process. By adopting the Millisecond Mindset, you build a foundation of respect for your user's time, which is the cornerstone of any great digital product.

>

A person is sketching on a tablet.

The Millisecond Mindset: A Front-End Performance Checklist

In front-end development, "fast" is a relative term. But to a user, it's binary. An interface is either instant, or it's "laggy." The line between these two perceptions is not measured in seconds, but in milliseconds. This is the Millisecond Mindset: a development philosophy where every line of code, every asset, and every network request is weighed against its impact on the user's perception of speed.

Human-computer interaction studies, famously by the Nielsen Norman Group, give us a clear framework:

  • 100ms (0.1 seconds): The limit for an action to feel "instant." The user feels like they are directly manipulating the UI.

  • 1 second: The limit for a user's flow of thought to remain uninterrupted. If they have to wait longer, their attention begins to drift.

  • 10 seconds: The absolute limit for keeping a user's attention. Anything longer, and the user is likely to abandon the task or the site entirely.

In an era of 5G and fiber optics, it's tempting to think performance is a solved problem. It's not. Mobile networks are unreliable, devices have varying processing power, and our applications are more complex than ever. Adopting the Millisecond Mindset means shifting from "does it work?" to "does it feel instant?" It’s the difference between a usable product and a delightful one. It directly impacts conversion rates, bounce rates, and user satisfaction.

Here is a performance checklist to help you get there:


1. Asset Optimization (The "Weight")


Every byte counts. The less you send over the wire, the faster the page will load, especially on mobile networks.

  • Images: This is often the biggest offender. Use modern formats like WebP or AVIF, which offer superior compression and quality compared to JPEG or PNG. Use responsive images (<picture> tag or srcset) to serve different sizes for different screens. Never use a 2000px image for a 200px thumbnail.

  • Minification: Minify your CSS and JavaScript. This process removes all whitespace, comments, and shortens variable names, shrinking the file size without changing the logic.

  • Compression: Ensure your server is using Gzip or, even better, Brotli compression to compress assets before sending them.

  • Fonts: Web fonts can be heavy. Use formats like WOFF2 and consider font subsetting—a technique where you only include the characters you actually need (e.g., just Latin characters) instead of the entire font file.


2. Network & Caching (The "Requests")


A site can be slow not because its files are big, but because it has to make too many trips to get them.

  • Reduce Requests: While HTTP/2 and HTTP/3 have made "request bundling" less critical, it's still a good practice. Limit third-party scripts (analytics, ads, trackers) as each one adds a new domain lookup and download.

  • Leverage a CDN: A Content Delivery Network (CDN) hosts your static assets (images, CSS, JS) on servers all over the world. This means a user in Tokyo downloads assets from a server in Asia, not from your main server in Virginia, drastically reducing latency.

  • Master Caching: Use the Cache-Control HTTP header to tell the browser how long it can "remember" a file. Your logo, CSS, and fonts probably don't change for weeks. Caching them means a return visitor loads the page almost instantly.

  • Service Workers: For the ultimate in performance, a Service Worker can cache your entire app "shell," allowing your site to load offline or near-instantly on repeat visits (a core part of a Progressive Web App, or PWA).


3. Render Path Optimization (The "Sequence")


How the browser builds your page is just as important as how fast it gets the files.

  • The Critical Path: Prioritize loading only what the user needs to see "above the fold" (the visible part of the screen).

  • Critical CSS: This technique involves inlining the absolute minimum CSS needed to style the above-the-fold content directly in the <head> of your HTML. This allows the browser to start painting the page immediately, while the rest of your styles.css loads non-blockingly.

  • Script Loading: Never just drop a <script> tag in your <head> unless you have a very good reason. Use the defer attribute to download the script while the HTML is parsing and execute it after the document is built. Use async for non-essential third-party scripts that can run whenever they're ready.

  • Code Splitting: Don't ship your entire JavaScript application in one giant app.js file. Use your bundler (like Webpack or Vite) to split your code by route or component. This way, a user visiting your homepage only downloads the JavaScript for the homepage.

Performance isn't a feature you add at the end. It's a fundamental part of the design and engineering process. By adopting the Millisecond Mindset, you build a foundation of respect for your user's time, which is the cornerstone of any great digital product.

>

A person is sketching on a tablet.

The Millisecond Mindset: A Front-End Performance Checklist

In front-end development, "fast" is a relative term. But to a user, it's binary. An interface is either instant, or it's "laggy." The line between these two perceptions is not measured in seconds, but in milliseconds. This is the Millisecond Mindset: a development philosophy where every line of code, every asset, and every network request is weighed against its impact on the user's perception of speed.

Human-computer interaction studies, famously by the Nielsen Norman Group, give us a clear framework:

  • 100ms (0.1 seconds): The limit for an action to feel "instant." The user feels like they are directly manipulating the UI.

  • 1 second: The limit for a user's flow of thought to remain uninterrupted. If they have to wait longer, their attention begins to drift.

  • 10 seconds: The absolute limit for keeping a user's attention. Anything longer, and the user is likely to abandon the task or the site entirely.

In an era of 5G and fiber optics, it's tempting to think performance is a solved problem. It's not. Mobile networks are unreliable, devices have varying processing power, and our applications are more complex than ever. Adopting the Millisecond Mindset means shifting from "does it work?" to "does it feel instant?" It’s the difference between a usable product and a delightful one. It directly impacts conversion rates, bounce rates, and user satisfaction.

Here is a performance checklist to help you get there:


1. Asset Optimization (The "Weight")


Every byte counts. The less you send over the wire, the faster the page will load, especially on mobile networks.

  • Images: This is often the biggest offender. Use modern formats like WebP or AVIF, which offer superior compression and quality compared to JPEG or PNG. Use responsive images (<picture> tag or srcset) to serve different sizes for different screens. Never use a 2000px image for a 200px thumbnail.

  • Minification: Minify your CSS and JavaScript. This process removes all whitespace, comments, and shortens variable names, shrinking the file size without changing the logic.

  • Compression: Ensure your server is using Gzip or, even better, Brotli compression to compress assets before sending them.

  • Fonts: Web fonts can be heavy. Use formats like WOFF2 and consider font subsetting—a technique where you only include the characters you actually need (e.g., just Latin characters) instead of the entire font file.


2. Network & Caching (The "Requests")


A site can be slow not because its files are big, but because it has to make too many trips to get them.

  • Reduce Requests: While HTTP/2 and HTTP/3 have made "request bundling" less critical, it's still a good practice. Limit third-party scripts (analytics, ads, trackers) as each one adds a new domain lookup and download.

  • Leverage a CDN: A Content Delivery Network (CDN) hosts your static assets (images, CSS, JS) on servers all over the world. This means a user in Tokyo downloads assets from a server in Asia, not from your main server in Virginia, drastically reducing latency.

  • Master Caching: Use the Cache-Control HTTP header to tell the browser how long it can "remember" a file. Your logo, CSS, and fonts probably don't change for weeks. Caching them means a return visitor loads the page almost instantly.

  • Service Workers: For the ultimate in performance, a Service Worker can cache your entire app "shell," allowing your site to load offline or near-instantly on repeat visits (a core part of a Progressive Web App, or PWA).


3. Render Path Optimization (The "Sequence")


How the browser builds your page is just as important as how fast it gets the files.

  • The Critical Path: Prioritize loading only what the user needs to see "above the fold" (the visible part of the screen).

  • Critical CSS: This technique involves inlining the absolute minimum CSS needed to style the above-the-fold content directly in the <head> of your HTML. This allows the browser to start painting the page immediately, while the rest of your styles.css loads non-blockingly.

  • Script Loading: Never just drop a <script> tag in your <head> unless you have a very good reason. Use the defer attribute to download the script while the HTML is parsing and execute it after the document is built. Use async for non-essential third-party scripts that can run whenever they're ready.

  • Code Splitting: Don't ship your entire JavaScript application in one giant app.js file. Use your bundler (like Webpack or Vite) to split your code by route or component. This way, a user visiting your homepage only downloads the JavaScript for the homepage.

Performance isn't a feature you add at the end. It's a fundamental part of the design and engineering process. By adopting the Millisecond Mindset, you build a foundation of respect for your user's time, which is the cornerstone of any great digital product.

>

a computer screen with a logo on it

Beyond useEffect: Mastering React's useMemo and useCallback

For many React developers, useState and useEffect are the first hooks we master. They allow us to manage state and handle side effects, forming the core of any dynamic component. But as applications grow in complexity, we often hit a performance wall. Components re-render too often, UIs feel sluggish, and we start to wonder what we're doing wrong. The problem, often, isn't that React is slow; it's that we are telling it to do unnecessary work.

This is where useMemo and useCallback come in. They are not magic bullets, but precision tools for optimization. To understand them, you must first understand memoization and referential equality.

  • Memoization: A fancy term for "remembering" the result of a calculation. If you give a function the same inputs, it should give you the same output. Memoization is simply caching that output so you don't have to re-run the expensive calculation every time.

  • Referential Equality: In JavaScript, [] === [] is false. Two objects or arrays are only "equal" if they are the exact same instance in memory. This is critical, because React's re-render logic relies on checking if props or state have "changed." A new (but identical) array or function will be seen as a "change," triggering a re-render.


useMemo: Remembering a Value

The Problem: Imagine you have a component that displays a list of 10,000 items. It also has a filter input (stored in state) that filters this list.

JavaScript

function MyBigList({ bigList }) {
  const [filter, setFilter] = useState('');
  
  // 🚩 Problem: This runs on EVERY single render.
  // Even if you just type in a "search" box that
  // has nothing to do with this list.
  const filteredList = bigList.filter(item => item.name.includes(filter));

  // ...
}

If this component re-renders for any reason (e.g., a parent component's state changes), that expensive .filter() operation on 10,000 items will run again, even if bigList and filter haven't changed.

The SolutionuseMemo

useMemo memoizes (remembers) the result of this calculation. It takes two arguments: a function that does the calculation, and a dependency array.

JavaScript

import React, { useMemo, useState } from 'react';

function MyBigList({ bigList }) {
  const [filter, setFilter] = useState('');

  // ✅ Solution: This function ONLY re-runs if 
  // `bigList` or `filter` changes.
  const filteredList = useMemo(() => {
    return bigList.filter(item => item.name.includes(filter));
  }, [bigList, filter]); // The dependency array

  // ...
}

Now, if the component re-renders for an unrelated reason, React will skip the calculation and just return the remembered filteredList. It's a huge performance win.


useCallback: Remembering a Function

The Problem: This one is more subtle. It's about referential equality.

JavaScript

function Parent() {
  const [count, setCount] = useState(0);

  // 🚩 Problem: A new `handleClick` function is
  // created on EVERY render of `Parent`.
  const handleClick = () => {
    console.log('Button clicked!');
  };

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      
      {/* MyMemoizedChild will re-render even if its props
          look the same, because `handleClick` is a new function */}
      <MyMemoizedChild onClick={handleClick} />
    </>
  );
}

const MyMemoizedChild = React.memo(({ onClick }) => {
  // This component is wrapped in React.memo
  // to prevent re-renders if props don't change.
  return <button onClick={onClick}>Click Me</button>;
});

Here, MyMemoizedChild is wrapped in React.memo, which is supposed to prevent it from re-rendering if its props are the same. But every time Parent re-renders (like when you click the "Count" button), a brand new handleClick function is created. From MyMemoizedChild's perspective, its onClick prop has changed (it's a new function reference), so it re-renders unnecessarily.

The Solution: useCallback useCallback memoizes the function instance itself.

import React, { useCallback, useState } from 'react';

function Parent() {
  const [count, setCount] = useState(0);

  // ✅ Solution: `handleClick` is now the *same function*
  // across re-renders, unless its dependencies change.
  const handleClick = useCallback(() => {
    console.log('Button clicked!');
  }, []); // Empty array = never changes

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      
      {/* Now, `MyMemoizedChild` gets the same prop and
          `React.memo` can successfully skip the re-render. */}
      <MyMemoizedChild onClick={handleClick} />
    </>
  );
}

By using useCallback, Parent now gives MyMemoizedChild the exact same function instance every time, as long as its dependencies (in this case, none) don't change. React.memo can now correctly compare the props, see they are identical, and skip the re-render.

Don't go wrapping every function in useCallback or every value in useMemo. They have a small cost. Use them as precision tools. Profile your app with the React DevTools, find the slow components, and then apply these hooks to solve real, measurable performance bottlenecks.

>

a computer screen with a logo on it

Beyond useEffect: Mastering React's useMemo and useCallback

For many React developers, useState and useEffect are the first hooks we master. They allow us to manage state and handle side effects, forming the core of any dynamic component. But as applications grow in complexity, we often hit a performance wall. Components re-render too often, UIs feel sluggish, and we start to wonder what we're doing wrong. The problem, often, isn't that React is slow; it's that we are telling it to do unnecessary work.

This is where useMemo and useCallback come in. They are not magic bullets, but precision tools for optimization. To understand them, you must first understand memoization and referential equality.

  • Memoization: A fancy term for "remembering" the result of a calculation. If you give a function the same inputs, it should give you the same output. Memoization is simply caching that output so you don't have to re-run the expensive calculation every time.

  • Referential Equality: In JavaScript, [] === [] is false. Two objects or arrays are only "equal" if they are the exact same instance in memory. This is critical, because React's re-render logic relies on checking if props or state have "changed." A new (but identical) array or function will be seen as a "change," triggering a re-render.


useMemo: Remembering a Value

The Problem: Imagine you have a component that displays a list of 10,000 items. It also has a filter input (stored in state) that filters this list.

JavaScript

function MyBigList({ bigList }) {
  const [filter, setFilter] = useState('');
  
  // 🚩 Problem: This runs on EVERY single render.
  // Even if you just type in a "search" box that
  // has nothing to do with this list.
  const filteredList = bigList.filter(item => item.name.includes(filter));

  // ...
}

If this component re-renders for any reason (e.g., a parent component's state changes), that expensive .filter() operation on 10,000 items will run again, even if bigList and filter haven't changed.

The SolutionuseMemo

useMemo memoizes (remembers) the result of this calculation. It takes two arguments: a function that does the calculation, and a dependency array.

JavaScript

import React, { useMemo, useState } from 'react';

function MyBigList({ bigList }) {
  const [filter, setFilter] = useState('');

  // ✅ Solution: This function ONLY re-runs if 
  // `bigList` or `filter` changes.
  const filteredList = useMemo(() => {
    return bigList.filter(item => item.name.includes(filter));
  }, [bigList, filter]); // The dependency array

  // ...
}

Now, if the component re-renders for an unrelated reason, React will skip the calculation and just return the remembered filteredList. It's a huge performance win.


useCallback: Remembering a Function

The Problem: This one is more subtle. It's about referential equality.

JavaScript

function Parent() {
  const [count, setCount] = useState(0);

  // 🚩 Problem: A new `handleClick` function is
  // created on EVERY render of `Parent`.
  const handleClick = () => {
    console.log('Button clicked!');
  };

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      
      {/* MyMemoizedChild will re-render even if its props
          look the same, because `handleClick` is a new function */}
      <MyMemoizedChild onClick={handleClick} />
    </>
  );
}

const MyMemoizedChild = React.memo(({ onClick }) => {
  // This component is wrapped in React.memo
  // to prevent re-renders if props don't change.
  return <button onClick={onClick}>Click Me</button>;
});

Here, MyMemoizedChild is wrapped in React.memo, which is supposed to prevent it from re-rendering if its props are the same. But every time Parent re-renders (like when you click the "Count" button), a brand new handleClick function is created. From MyMemoizedChild's perspective, its onClick prop has changed (it's a new function reference), so it re-renders unnecessarily.

The Solution: useCallback useCallback memoizes the function instance itself.

import React, { useCallback, useState } from 'react';

function Parent() {
  const [count, setCount] = useState(0);

  // ✅ Solution: `handleClick` is now the *same function*
  // across re-renders, unless its dependencies change.
  const handleClick = useCallback(() => {
    console.log('Button clicked!');
  }, []); // Empty array = never changes

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      
      {/* Now, `MyMemoizedChild` gets the same prop and
          `React.memo` can successfully skip the re-render. */}
      <MyMemoizedChild onClick={handleClick} />
    </>
  );
}

By using useCallback, Parent now gives MyMemoizedChild the exact same function instance every time, as long as its dependencies (in this case, none) don't change. React.memo can now correctly compare the props, see they are identical, and skip the re-render.

Don't go wrapping every function in useCallback or every value in useMemo. They have a small cost. Use them as precision tools. Profile your app with the React DevTools, find the slow components, and then apply these hooks to solve real, measurable performance bottlenecks.

>

a computer screen with a logo on it

Beyond useEffect: Mastering React's useMemo and useCallback

For many React developers, useState and useEffect are the first hooks we master. They allow us to manage state and handle side effects, forming the core of any dynamic component. But as applications grow in complexity, we often hit a performance wall. Components re-render too often, UIs feel sluggish, and we start to wonder what we're doing wrong. The problem, often, isn't that React is slow; it's that we are telling it to do unnecessary work.

This is where useMemo and useCallback come in. They are not magic bullets, but precision tools for optimization. To understand them, you must first understand memoization and referential equality.

  • Memoization: A fancy term for "remembering" the result of a calculation. If you give a function the same inputs, it should give you the same output. Memoization is simply caching that output so you don't have to re-run the expensive calculation every time.

  • Referential Equality: In JavaScript, [] === [] is false. Two objects or arrays are only "equal" if they are the exact same instance in memory. This is critical, because React's re-render logic relies on checking if props or state have "changed." A new (but identical) array or function will be seen as a "change," triggering a re-render.


useMemo: Remembering a Value

The Problem: Imagine you have a component that displays a list of 10,000 items. It also has a filter input (stored in state) that filters this list.

JavaScript

function MyBigList({ bigList }) {
  const [filter, setFilter] = useState('');
  
  // 🚩 Problem: This runs on EVERY single render.
  // Even if you just type in a "search" box that
  // has nothing to do with this list.
  const filteredList = bigList.filter(item => item.name.includes(filter));

  // ...
}

If this component re-renders for any reason (e.g., a parent component's state changes), that expensive .filter() operation on 10,000 items will run again, even if bigList and filter haven't changed.

The SolutionuseMemo

useMemo memoizes (remembers) the result of this calculation. It takes two arguments: a function that does the calculation, and a dependency array.

JavaScript

import React, { useMemo, useState } from 'react';

function MyBigList({ bigList }) {
  const [filter, setFilter] = useState('');

  // ✅ Solution: This function ONLY re-runs if 
  // `bigList` or `filter` changes.
  const filteredList = useMemo(() => {
    return bigList.filter(item => item.name.includes(filter));
  }, [bigList, filter]); // The dependency array

  // ...
}

Now, if the component re-renders for an unrelated reason, React will skip the calculation and just return the remembered filteredList. It's a huge performance win.


useCallback: Remembering a Function

The Problem: This one is more subtle. It's about referential equality.

JavaScript

function Parent() {
  const [count, setCount] = useState(0);

  // 🚩 Problem: A new `handleClick` function is
  // created on EVERY render of `Parent`.
  const handleClick = () => {
    console.log('Button clicked!');
  };

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      
      {/* MyMemoizedChild will re-render even if its props
          look the same, because `handleClick` is a new function */}
      <MyMemoizedChild onClick={handleClick} />
    </>
  );
}

const MyMemoizedChild = React.memo(({ onClick }) => {
  // This component is wrapped in React.memo
  // to prevent re-renders if props don't change.
  return <button onClick={onClick}>Click Me</button>;
});

Here, MyMemoizedChild is wrapped in React.memo, which is supposed to prevent it from re-rendering if its props are the same. But every time Parent re-renders (like when you click the "Count" button), a brand new handleClick function is created. From MyMemoizedChild's perspective, its onClick prop has changed (it's a new function reference), so it re-renders unnecessarily.

The Solution: useCallback useCallback memoizes the function instance itself.

import React, { useCallback, useState } from 'react';

function Parent() {
  const [count, setCount] = useState(0);

  // ✅ Solution: `handleClick` is now the *same function*
  // across re-renders, unless its dependencies change.
  const handleClick = useCallback(() => {
    console.log('Button clicked!');
  }, []); // Empty array = never changes

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      
      {/* Now, `MyMemoizedChild` gets the same prop and
          `React.memo` can successfully skip the re-render. */}
      <MyMemoizedChild onClick={handleClick} />
    </>
  );
}

By using useCallback, Parent now gives MyMemoizedChild the exact same function instance every time, as long as its dependencies (in this case, none) don't change. React.memo can now correctly compare the props, see they are identical, and skip the re-render.

Don't go wrapping every function in useCallback or every value in useMemo. They have a small cost. Use them as precision tools. Profile your app with the React DevTools, find the slow components, and then apply these hooks to solve real, measurable performance bottlenecks.

>

programming language

Why Tailwind CSS (Almost) Always Wins

For decades, the story of CSS has been a story of abstraction. We went from plain CSS files to methodologies like BEM (Block, Element, Modifier) to manage complexity. Then came pre-processors like SASS to give us variables and mixins. Then came CSS-in-JS to colocate styles with components. Each step solved a problem but often introduced a new one: BEM led to long, rigid class names; SASS at scale became a web of @includes; CSS-in-JS added runtime overhead.

Then, Tailwind CSS entered the scene and proposed a radical idea: what if we stopped writing CSS and just used "utility" classes?

Tailwind is a utility-first framework. Unlike Bootstrap or Foundation, it doesn't give you pre-built components like .card or .navbar. Instead, it gives you thousands of tiny, single-purpose classes like flex, p-4 (padding: 1rem), rounded-lg (border-radius: 0.5rem), and text-red-500.

To many, this looks like a nightmare. "This is just inline styles!" is the common refrain. But once you work with it, you realize its profound advantages.


1. The End of Context Switching

This is the biggest win. Your styling happens directly in your HTML or JSX. You no longer have to:

  1. Think of a "semantic" class name (like .user-profile-card-header-title).

  2. Tab over to your styles.css or StyledComponents.js file.

  3. Find or create the selector.

  4. Write the CSS.

  5. Tab back to your HTML and hope you spelled the class name correctly.

With Tailwind, you just write: <h1 class="text-2xl font-bold text-gray-900">. The "flow state" this enables is incredible. You can build complex, custom UIs without ever leaving your markup.


2. Design Constraints are a Feature, Not a Bug

How many times have you seen margin-left: 13px; in a codebase? These "magic numbers" lead to an inconsistent, messy UI. Tailwind is built on a comprehensive design system, defined in your tailwind.config.js file.

The p-4 class doesn't just mean padding: 1rem. It means "padding, level 4." p-6 is 1.5rem. This configurable, constrained scale ensures your entire application is visually consistent. Your spacing, typography, and color palette are all cohesive by default. This makes it easier to build a professional-looking site.


3. Phenomenal Responsiveness and State Handling

This is where Tailwind truly shines. Want an element to be a flex column on mobile but a flex row on medium-sized screens? <div class="flex flex-col md:flex-row"> That's it. No media queries in a separate file. The logic is right there.

Want a button that changes color on hover and focus? <button class="bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2"> This system is simple, intuitive, and covers everything from dark mode (dark:bg-gray-800) to disabled states.


4. Your CSS Bundle is Tiny

This is counter-intuitive. How can a framework with thousands of classes be performant? The magic is in PostCSS. When you build for production, Tailwind scans your files, finds only the classes you actually used, and generates a static CSS file containing just those.

A typical Tailwind-built site ships a CSS file that is often under 10kb. You get all the developer experience of a massive framework with none of the production bloat.


So, Why "Almost"?

No tool is perfect. The main criticism is valid: "It makes my HTML cluttered." A component with 15 utility classes on it can look verbose and hard to read. <div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4"> That's a lot. However, this argument misses the point of a component-based framework like React or Vue. You don't leave it like that. You do this:

  1. You build the component once with all the utilities.

  2. You extract it into its own component, like <UserProfileCard>.

  3. You never look at that long string of classes again.

The "clutter" is encapsulated, and you're left with clean, reusable components. For new developers, there's also a learning curve—not of CSS, but of Tailwind's class names. But this is a small price to pay for the massive boost in productivity, consistency, and performance. It's a trade-off that, for most projects, (almost) always wins.

>

programming language

Why Tailwind CSS (Almost) Always Wins

For decades, the story of CSS has been a story of abstraction. We went from plain CSS files to methodologies like BEM (Block, Element, Modifier) to manage complexity. Then came pre-processors like SASS to give us variables and mixins. Then came CSS-in-JS to colocate styles with components. Each step solved a problem but often introduced a new one: BEM led to long, rigid class names; SASS at scale became a web of @includes; CSS-in-JS added runtime overhead.

Then, Tailwind CSS entered the scene and proposed a radical idea: what if we stopped writing CSS and just used "utility" classes?

Tailwind is a utility-first framework. Unlike Bootstrap or Foundation, it doesn't give you pre-built components like .card or .navbar. Instead, it gives you thousands of tiny, single-purpose classes like flex, p-4 (padding: 1rem), rounded-lg (border-radius: 0.5rem), and text-red-500.

To many, this looks like a nightmare. "This is just inline styles!" is the common refrain. But once you work with it, you realize its profound advantages.


1. The End of Context Switching

This is the biggest win. Your styling happens directly in your HTML or JSX. You no longer have to:

  1. Think of a "semantic" class name (like .user-profile-card-header-title).

  2. Tab over to your styles.css or StyledComponents.js file.

  3. Find or create the selector.

  4. Write the CSS.

  5. Tab back to your HTML and hope you spelled the class name correctly.

With Tailwind, you just write: <h1 class="text-2xl font-bold text-gray-900">. The "flow state" this enables is incredible. You can build complex, custom UIs without ever leaving your markup.


2. Design Constraints are a Feature, Not a Bug

How many times have you seen margin-left: 13px; in a codebase? These "magic numbers" lead to an inconsistent, messy UI. Tailwind is built on a comprehensive design system, defined in your tailwind.config.js file.

The p-4 class doesn't just mean padding: 1rem. It means "padding, level 4." p-6 is 1.5rem. This configurable, constrained scale ensures your entire application is visually consistent. Your spacing, typography, and color palette are all cohesive by default. This makes it easier to build a professional-looking site.


3. Phenomenal Responsiveness and State Handling

This is where Tailwind truly shines. Want an element to be a flex column on mobile but a flex row on medium-sized screens? <div class="flex flex-col md:flex-row"> That's it. No media queries in a separate file. The logic is right there.

Want a button that changes color on hover and focus? <button class="bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2"> This system is simple, intuitive, and covers everything from dark mode (dark:bg-gray-800) to disabled states.


4. Your CSS Bundle is Tiny

This is counter-intuitive. How can a framework with thousands of classes be performant? The magic is in PostCSS. When you build for production, Tailwind scans your files, finds only the classes you actually used, and generates a static CSS file containing just those.

A typical Tailwind-built site ships a CSS file that is often under 10kb. You get all the developer experience of a massive framework with none of the production bloat.


So, Why "Almost"?

No tool is perfect. The main criticism is valid: "It makes my HTML cluttered." A component with 15 utility classes on it can look verbose and hard to read. <div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4"> That's a lot. However, this argument misses the point of a component-based framework like React or Vue. You don't leave it like that. You do this:

  1. You build the component once with all the utilities.

  2. You extract it into its own component, like <UserProfileCard>.

  3. You never look at that long string of classes again.

The "clutter" is encapsulated, and you're left with clean, reusable components. For new developers, there's also a learning curve—not of CSS, but of Tailwind's class names. But this is a small price to pay for the massive boost in productivity, consistency, and performance. It's a trade-off that, for most projects, (almost) always wins.

>

programming language

Why Tailwind CSS (Almost) Always Wins

For decades, the story of CSS has been a story of abstraction. We went from plain CSS files to methodologies like BEM (Block, Element, Modifier) to manage complexity. Then came pre-processors like SASS to give us variables and mixins. Then came CSS-in-JS to colocate styles with components. Each step solved a problem but often introduced a new one: BEM led to long, rigid class names; SASS at scale became a web of @includes; CSS-in-JS added runtime overhead.

Then, Tailwind CSS entered the scene and proposed a radical idea: what if we stopped writing CSS and just used "utility" classes?

Tailwind is a utility-first framework. Unlike Bootstrap or Foundation, it doesn't give you pre-built components like .card or .navbar. Instead, it gives you thousands of tiny, single-purpose classes like flex, p-4 (padding: 1rem), rounded-lg (border-radius: 0.5rem), and text-red-500.

To many, this looks like a nightmare. "This is just inline styles!" is the common refrain. But once you work with it, you realize its profound advantages.


1. The End of Context Switching

This is the biggest win. Your styling happens directly in your HTML or JSX. You no longer have to:

  1. Think of a "semantic" class name (like .user-profile-card-header-title).

  2. Tab over to your styles.css or StyledComponents.js file.

  3. Find or create the selector.

  4. Write the CSS.

  5. Tab back to your HTML and hope you spelled the class name correctly.

With Tailwind, you just write: <h1 class="text-2xl font-bold text-gray-900">. The "flow state" this enables is incredible. You can build complex, custom UIs without ever leaving your markup.


2. Design Constraints are a Feature, Not a Bug

How many times have you seen margin-left: 13px; in a codebase? These "magic numbers" lead to an inconsistent, messy UI. Tailwind is built on a comprehensive design system, defined in your tailwind.config.js file.

The p-4 class doesn't just mean padding: 1rem. It means "padding, level 4." p-6 is 1.5rem. This configurable, constrained scale ensures your entire application is visually consistent. Your spacing, typography, and color palette are all cohesive by default. This makes it easier to build a professional-looking site.


3. Phenomenal Responsiveness and State Handling

This is where Tailwind truly shines. Want an element to be a flex column on mobile but a flex row on medium-sized screens? <div class="flex flex-col md:flex-row"> That's it. No media queries in a separate file. The logic is right there.

Want a button that changes color on hover and focus? <button class="bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2"> This system is simple, intuitive, and covers everything from dark mode (dark:bg-gray-800) to disabled states.


4. Your CSS Bundle is Tiny

This is counter-intuitive. How can a framework with thousands of classes be performant? The magic is in PostCSS. When you build for production, Tailwind scans your files, finds only the classes you actually used, and generates a static CSS file containing just those.

A typical Tailwind-built site ships a CSS file that is often under 10kb. You get all the developer experience of a massive framework with none of the production bloat.


So, Why "Almost"?

No tool is perfect. The main criticism is valid: "It makes my HTML cluttered." A component with 15 utility classes on it can look verbose and hard to read. <div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4"> That's a lot. However, this argument misses the point of a component-based framework like React or Vue. You don't leave it like that. You do this:

  1. You build the component once with all the utilities.

  2. You extract it into its own component, like <UserProfileCard>.

  3. You never look at that long string of classes again.

The "clutter" is encapsulated, and you're left with clean, reusable components. For new developers, there's also a learning curve—not of CSS, but of Tailwind's class names. But this is a small price to pay for the massive boost in productivity, consistency, and performance. It's a trade-off that, for most projects, (almost) always wins.

>

blue red and green letters illustration

From CRA to App Router: A Guide to Migrating to Next.js

For years, create-react-app (CRA) was the undisputed king. It was the npx create-react-app my-app that launched a thousand projects. It gave us a modern, client-side-rendered (CSR) React setup with zero configuration. But the web evolved. The need for better SEO, faster initial page loads, and more integrated full-stack experiences grew. CRA, being purely client-side, started to show its age.

Enter Next.js, the full-stack React framework. And more recently, its new paradigm: the App Router.

Migrating from CRA to the Next.js App Router isn't just a "find and replace." It's a fundamental shift from a client-centric application to a server-centric one. This shift unlocks powerful features like Server-Side Rendering (SSR), Static Site Generation (SSG), and, most importantly, React Server Components (RSCs). The result is a faster, more powerful, and more SEO-friendly application.

Here’s a high-level guide to making the switch.


1. Setup and Folder Structure

You'll start by scaffolding a new Next.js project: npx create-next-app@latest. Choose "Yes" for using the app/ directory.

The folder structure is the first major change:

  • CRA: src/pages/, src/components/, public/index.html

  • Next.js (App Router): app/, public/ (no index.html), and you can keep a components/ folder.

Your App.js and index.css from CRA will conceptually merge into app/layout.jsx and app/globals.css. The app/layout.jsx is your new root, and it must define the <html> and <body> tags.


2. The New File-Based Routing

This is the biggest change. CRA used react-router-dom to define routes in a central file. The App Router uses a directory-based system.

CRA (react-router)

Next.js (App Router)

<Route path="/" element={<Home />}>

app/page.jsx

<Route path="/about" element={<About />}>

app/about/page.jsx

<Route path="/blog/:id" element={<Post />}>

app/blog/[id]/page.jsx

layout.jsx with <Outlet />

app/dashboard/layout.jsx (wraps routes in dashboard/)

To migrate, you will:

  1. Identify your "pages" in CRA.

  2. For each page, create a new folder in the app/ directory.

  3. Inside that folder, move your page component's content into a new file named page.jsx.

  4. Replace all react-router-dom's <Link> components with <Link> from next/link.


3. The Data Fetching Paradigm Shift

In CRA, you fetched data on the client inside a useEffect:

JavaScript

// CRA (Client-Side)
useEffect(() => {
  fetch('/api/posts')
    .then(res => res.json())
    .then(data => setPosts(data));
}, []);

This is slow. The user gets a loading spinner while they wait for the JavaScript to load, then wait for the data to be fetched.

With the App Router, components are Server Components by default. This means they run on the server. You can fetch data directly inside them using async/await.

JavaScript

// Next.js (Server Component)
async function getPosts() {
  const res = await fetch('https://api.example.com/posts');
  return res.json();
}

export default async function BlogPage() {
  const posts = await getPosts(); // This happens on the server!

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

The user's browser receives the fully-rendered HTML with the data already included. There is no loading state. It's dramatically faster and perfect for SEO.


4. "use client": The Client-Side Opt-In

"Wait," you say, "what about useState or onClick? They don't work on the server!"

You are correct. This is the most important concept: Server Components cannot use hooks or interactivity.

When you need a component to be interactive (use state, effects, or event listeners), you must explicitly mark it as a Client Component by placing the "use client" directive at the very top of the file.

JavaScript

// app/components/Counter.jsx
"use client"; // This is now a Client Component

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Your app/blog/page.jsx (Server Component) can then import and render this Counter (Client Component). This is the "hybrid" model: fetch data on the server, and send interactive "islands" of JavaScript to the client.

The migration is an investment. You're not just moving files; you're re-architecting your application to be faster, more efficient, and more powerful.

>

blue red and green letters illustration

From CRA to App Router: A Guide to Migrating to Next.js

For years, create-react-app (CRA) was the undisputed king. It was the npx create-react-app my-app that launched a thousand projects. It gave us a modern, client-side-rendered (CSR) React setup with zero configuration. But the web evolved. The need for better SEO, faster initial page loads, and more integrated full-stack experiences grew. CRA, being purely client-side, started to show its age.

Enter Next.js, the full-stack React framework. And more recently, its new paradigm: the App Router.

Migrating from CRA to the Next.js App Router isn't just a "find and replace." It's a fundamental shift from a client-centric application to a server-centric one. This shift unlocks powerful features like Server-Side Rendering (SSR), Static Site Generation (SSG), and, most importantly, React Server Components (RSCs). The result is a faster, more powerful, and more SEO-friendly application.

Here’s a high-level guide to making the switch.


1. Setup and Folder Structure

You'll start by scaffolding a new Next.js project: npx create-next-app@latest. Choose "Yes" for using the app/ directory.

The folder structure is the first major change:

  • CRA: src/pages/, src/components/, public/index.html

  • Next.js (App Router): app/, public/ (no index.html), and you can keep a components/ folder.

Your App.js and index.css from CRA will conceptually merge into app/layout.jsx and app/globals.css. The app/layout.jsx is your new root, and it must define the <html> and <body> tags.


2. The New File-Based Routing

This is the biggest change. CRA used react-router-dom to define routes in a central file. The App Router uses a directory-based system.

CRA (react-router)

Next.js (App Router)

<Route path="/" element={<Home />}>

app/page.jsx

<Route path="/about" element={<About />}>

app/about/page.jsx

<Route path="/blog/:id" element={<Post />}>

app/blog/[id]/page.jsx

layout.jsx with <Outlet />

app/dashboard/layout.jsx (wraps routes in dashboard/)

To migrate, you will:

  1. Identify your "pages" in CRA.

  2. For each page, create a new folder in the app/ directory.

  3. Inside that folder, move your page component's content into a new file named page.jsx.

  4. Replace all react-router-dom's <Link> components with <Link> from next/link.


3. The Data Fetching Paradigm Shift

In CRA, you fetched data on the client inside a useEffect:

JavaScript

// CRA (Client-Side)
useEffect(() => {
  fetch('/api/posts')
    .then(res => res.json())
    .then(data => setPosts(data));
}, []);

This is slow. The user gets a loading spinner while they wait for the JavaScript to load, then wait for the data to be fetched.

With the App Router, components are Server Components by default. This means they run on the server. You can fetch data directly inside them using async/await.

JavaScript

// Next.js (Server Component)
async function getPosts() {
  const res = await fetch('https://api.example.com/posts');
  return res.json();
}

export default async function BlogPage() {
  const posts = await getPosts(); // This happens on the server!

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

The user's browser receives the fully-rendered HTML with the data already included. There is no loading state. It's dramatically faster and perfect for SEO.


4. "use client": The Client-Side Opt-In

"Wait," you say, "what about useState or onClick? They don't work on the server!"

You are correct. This is the most important concept: Server Components cannot use hooks or interactivity.

When you need a component to be interactive (use state, effects, or event listeners), you must explicitly mark it as a Client Component by placing the "use client" directive at the very top of the file.

JavaScript

// app/components/Counter.jsx
"use client"; // This is now a Client Component

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Your app/blog/page.jsx (Server Component) can then import and render this Counter (Client Component). This is the "hybrid" model: fetch data on the server, and send interactive "islands" of JavaScript to the client.

The migration is an investment. You're not just moving files; you're re-architecting your application to be faster, more efficient, and more powerful.

>

blue red and green letters illustration

From CRA to App Router: A Guide to Migrating to Next.js

For years, create-react-app (CRA) was the undisputed king. It was the npx create-react-app my-app that launched a thousand projects. It gave us a modern, client-side-rendered (CSR) React setup with zero configuration. But the web evolved. The need for better SEO, faster initial page loads, and more integrated full-stack experiences grew. CRA, being purely client-side, started to show its age.

Enter Next.js, the full-stack React framework. And more recently, its new paradigm: the App Router.

Migrating from CRA to the Next.js App Router isn't just a "find and replace." It's a fundamental shift from a client-centric application to a server-centric one. This shift unlocks powerful features like Server-Side Rendering (SSR), Static Site Generation (SSG), and, most importantly, React Server Components (RSCs). The result is a faster, more powerful, and more SEO-friendly application.

Here’s a high-level guide to making the switch.


1. Setup and Folder Structure

You'll start by scaffolding a new Next.js project: npx create-next-app@latest. Choose "Yes" for using the app/ directory.

The folder structure is the first major change:

  • CRA: src/pages/, src/components/, public/index.html

  • Next.js (App Router): app/, public/ (no index.html), and you can keep a components/ folder.

Your App.js and index.css from CRA will conceptually merge into app/layout.jsx and app/globals.css. The app/layout.jsx is your new root, and it must define the <html> and <body> tags.


2. The New File-Based Routing

This is the biggest change. CRA used react-router-dom to define routes in a central file. The App Router uses a directory-based system.

CRA (react-router)

Next.js (App Router)

<Route path="/" element={<Home />}>

app/page.jsx

<Route path="/about" element={<About />}>

app/about/page.jsx

<Route path="/blog/:id" element={<Post />}>

app/blog/[id]/page.jsx

layout.jsx with <Outlet />

app/dashboard/layout.jsx (wraps routes in dashboard/)

To migrate, you will:

  1. Identify your "pages" in CRA.

  2. For each page, create a new folder in the app/ directory.

  3. Inside that folder, move your page component's content into a new file named page.jsx.

  4. Replace all react-router-dom's <Link> components with <Link> from next/link.


3. The Data Fetching Paradigm Shift

In CRA, you fetched data on the client inside a useEffect:

JavaScript

// CRA (Client-Side)
useEffect(() => {
  fetch('/api/posts')
    .then(res => res.json())
    .then(data => setPosts(data));
}, []);

This is slow. The user gets a loading spinner while they wait for the JavaScript to load, then wait for the data to be fetched.

With the App Router, components are Server Components by default. This means they run on the server. You can fetch data directly inside them using async/await.

JavaScript

// Next.js (Server Component)
async function getPosts() {
  const res = await fetch('https://api.example.com/posts');
  return res.json();
}

export default async function BlogPage() {
  const posts = await getPosts(); // This happens on the server!

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

The user's browser receives the fully-rendered HTML with the data already included. There is no loading state. It's dramatically faster and perfect for SEO.


4. "use client": The Client-Side Opt-In

"Wait," you say, "what about useState or onClick? They don't work on the server!"

You are correct. This is the most important concept: Server Components cannot use hooks or interactivity.

When you need a component to be interactive (use state, effects, or event listeners), you must explicitly mark it as a Client Component by placing the "use client" directive at the very top of the file.

JavaScript

// app/components/Counter.jsx
"use client"; // This is now a Client Component

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Your app/blog/page.jsx (Server Component) can then import and render this Counter (Client Component). This is the "hybrid" model: fetch data on the server, and send interactive "islands" of JavaScript to the client.

The migration is an investment. You're not just moving files; you're re-architecting your application to be faster, more efficient, and more powerful.

>

two hands touching each other in front of a pink background

Generative Art for Dummies: Building Your First p5.js Sketch in React

Generative art is a captivating discipline where the artist doesn't create the final piece directly. Instead, they write a set of rules—a program—and let the computer generate the art for them. The results can be unpredictable, complex, and beautiful.

p5.js is a beloved JavaScript library that makes this accessible to everyone. It's a "sketchbook" for the web, providing simple functions to draw shapes, handle colors, and respond to user input, all on an HTML canvas.

React, on the other hand, is a library for building user interfaces. It works by describing a declarative UI ("what the UI should look like"), and it manages its own "virtual" DOM.

The challenge is clear: p5.js is imperative—it wants to draw directly to the screen (createCanvas(), ellipse(), background()). React is declarative and hates when other things mess with the DOM it's supposed to be managing.

So how do we make them work together? We need to build a "bridge" or a "wrapper" component. This component will do two things:

  1. Create a stable div in React's DOM for p5.js to "own."

  2. Use React's lifecycle hooks (useEffect) to initialize and clean up the p5.js sketch, keeping it contained.

While there are libraries like react-p5 that do this for you, building it yourself once is the best way to understand exactly what's happening.

Here's how to build a simple, reusable p5.js wrapper component.


Step 1: Create the Wrapper Component

Create a new file, P5Sketch.jsx. We'll use useRef to get a direct reference to a DOM element, and useEffect to manage the p5.js lifecycle.

JavaScript

import React, { useRef, useEffect } from 'react';
import p5 from 'p5';

// The "sketch" function is our set of p5.js instructions.
// We'll pass this in as a prop.
const P5Sketch = ({ sketch }) => {
  // useRef gives us a way to "point" to the div we'll create.
  const sketchRef = useRef(null);

  useEffect(() => {
    // This is where the magic happens.
    // We create a new p5 instance and pass it our sketch function.
    // We also pass it the DOM node we want it to attach to.
    const p5Instance = new p5(sketch, sketchRef.current);

    // This is the "cleanup" function.
    // When the React component unmounts, this function
    // will run, telling p5 to remove the canvas.
    // This prevents memory leaks.
    return (). => {
      p5Instance.remove();
    };
  }, [sketch]); // Re-run the effect if the sketch function itself changes

  // This is the actual DOM element that React will render.
  // p5.js will attach its canvas inside this div.
  return <div ref={sketchRef} />;
};

export default P5Sketch;


Step 2: Write Your First "Sketch"

A p5.js sketch is just a function that takes one argument, typically called p (for "p5"). This p object contains all the p5.js drawing functions. The sketch must have at least a setup function and, for animations, a draw function.

Let's make a file for our sketch, MySketch.js.

JavaScript

// MySketch.js
export const sketch = (p) => {
  // `setup` runs once at the beginning
  p.setup = () => {
    // Create a 400x400 canvas.
    // p5.js will automatically attach it to the
    // element we provided in the wrapper.
    p.createCanvas(400, 400);
  };

  // `draw` runs 60 times per second (by default)
  p.draw = () => {
    // A simple pulsating circle
    p.background(10, 10, 20); // Dark blue background
    p.fill(255, 200, 0); // Bright yellow fill
    p.noStroke();

    // Use p.frameCount (current frame number) to animate
    // a sine wave for a smooth "breathing" effect.
    const diameter = 100 + p.sin(p.frameCount * 0.05) * 50;
    
    p.ellipse(p.width / 2, p.height / 2, diameter, diameter);
  };
};


Step 3: Use Your Sketch in Your App

Now, in your App.js or any other component, you can import and use your wrapper and your sketch.

JavaScript

import React from 'react';
import P5Sketch from './P5Sketch';
import { sketch } from './MySketch'; // Import the sketch function

function App() {
  return (
    <div style={{ display: 'grid', placeItems: 'center', minHeight: '100vh' }}>
      <h1>My First p5.js Sketch in React</h1>
      <P5Sketch sketch={sketch} />
    </div>
  );
}

export default App;

That's it! You now have a pulsating generative art piece running inside your React application. You can pass props to your P5Sketch wrapper to make your art interactive, responding to React's state. This combination gives you the best of both worlds: the robust component model of React and the creative, visual freedom of p5.js.

>

two hands touching each other in front of a pink background

Generative Art for Dummies: Building Your First p5.js Sketch in React

Generative art is a captivating discipline where the artist doesn't create the final piece directly. Instead, they write a set of rules—a program—and let the computer generate the art for them. The results can be unpredictable, complex, and beautiful.

p5.js is a beloved JavaScript library that makes this accessible to everyone. It's a "sketchbook" for the web, providing simple functions to draw shapes, handle colors, and respond to user input, all on an HTML canvas.

React, on the other hand, is a library for building user interfaces. It works by describing a declarative UI ("what the UI should look like"), and it manages its own "virtual" DOM.

The challenge is clear: p5.js is imperative—it wants to draw directly to the screen (createCanvas(), ellipse(), background()). React is declarative and hates when other things mess with the DOM it's supposed to be managing.

So how do we make them work together? We need to build a "bridge" or a "wrapper" component. This component will do two things:

  1. Create a stable div in React's DOM for p5.js to "own."

  2. Use React's lifecycle hooks (useEffect) to initialize and clean up the p5.js sketch, keeping it contained.

While there are libraries like react-p5 that do this for you, building it yourself once is the best way to understand exactly what's happening.

Here's how to build a simple, reusable p5.js wrapper component.


Step 1: Create the Wrapper Component

Create a new file, P5Sketch.jsx. We'll use useRef to get a direct reference to a DOM element, and useEffect to manage the p5.js lifecycle.

JavaScript

import React, { useRef, useEffect } from 'react';
import p5 from 'p5';

// The "sketch" function is our set of p5.js instructions.
// We'll pass this in as a prop.
const P5Sketch = ({ sketch }) => {
  // useRef gives us a way to "point" to the div we'll create.
  const sketchRef = useRef(null);

  useEffect(() => {
    // This is where the magic happens.
    // We create a new p5 instance and pass it our sketch function.
    // We also pass it the DOM node we want it to attach to.
    const p5Instance = new p5(sketch, sketchRef.current);

    // This is the "cleanup" function.
    // When the React component unmounts, this function
    // will run, telling p5 to remove the canvas.
    // This prevents memory leaks.
    return (). => {
      p5Instance.remove();
    };
  }, [sketch]); // Re-run the effect if the sketch function itself changes

  // This is the actual DOM element that React will render.
  // p5.js will attach its canvas inside this div.
  return <div ref={sketchRef} />;
};

export default P5Sketch;


Step 2: Write Your First "Sketch"

A p5.js sketch is just a function that takes one argument, typically called p (for "p5"). This p object contains all the p5.js drawing functions. The sketch must have at least a setup function and, for animations, a draw function.

Let's make a file for our sketch, MySketch.js.

JavaScript

// MySketch.js
export const sketch = (p) => {
  // `setup` runs once at the beginning
  p.setup = () => {
    // Create a 400x400 canvas.
    // p5.js will automatically attach it to the
    // element we provided in the wrapper.
    p.createCanvas(400, 400);
  };

  // `draw` runs 60 times per second (by default)
  p.draw = () => {
    // A simple pulsating circle
    p.background(10, 10, 20); // Dark blue background
    p.fill(255, 200, 0); // Bright yellow fill
    p.noStroke();

    // Use p.frameCount (current frame number) to animate
    // a sine wave for a smooth "breathing" effect.
    const diameter = 100 + p.sin(p.frameCount * 0.05) * 50;
    
    p.ellipse(p.width / 2, p.height / 2, diameter, diameter);
  };
};


Step 3: Use Your Sketch in Your App

Now, in your App.js or any other component, you can import and use your wrapper and your sketch.

JavaScript

import React from 'react';
import P5Sketch from './P5Sketch';
import { sketch } from './MySketch'; // Import the sketch function

function App() {
  return (
    <div style={{ display: 'grid', placeItems: 'center', minHeight: '100vh' }}>
      <h1>My First p5.js Sketch in React</h1>
      <P5Sketch sketch={sketch} />
    </div>
  );
}

export default App;

That's it! You now have a pulsating generative art piece running inside your React application. You can pass props to your P5Sketch wrapper to make your art interactive, responding to React's state. This combination gives you the best of both worlds: the robust component model of React and the creative, visual freedom of p5.js.

>

two hands touching each other in front of a pink background

Generative Art for Dummies: Building Your First p5.js Sketch in React

Generative art is a captivating discipline where the artist doesn't create the final piece directly. Instead, they write a set of rules—a program—and let the computer generate the art for them. The results can be unpredictable, complex, and beautiful.

p5.js is a beloved JavaScript library that makes this accessible to everyone. It's a "sketchbook" for the web, providing simple functions to draw shapes, handle colors, and respond to user input, all on an HTML canvas.

React, on the other hand, is a library for building user interfaces. It works by describing a declarative UI ("what the UI should look like"), and it manages its own "virtual" DOM.

The challenge is clear: p5.js is imperative—it wants to draw directly to the screen (createCanvas(), ellipse(), background()). React is declarative and hates when other things mess with the DOM it's supposed to be managing.

So how do we make them work together? We need to build a "bridge" or a "wrapper" component. This component will do two things:

  1. Create a stable div in React's DOM for p5.js to "own."

  2. Use React's lifecycle hooks (useEffect) to initialize and clean up the p5.js sketch, keeping it contained.

While there are libraries like react-p5 that do this for you, building it yourself once is the best way to understand exactly what's happening.

Here's how to build a simple, reusable p5.js wrapper component.


Step 1: Create the Wrapper Component

Create a new file, P5Sketch.jsx. We'll use useRef to get a direct reference to a DOM element, and useEffect to manage the p5.js lifecycle.

JavaScript

import React, { useRef, useEffect } from 'react';
import p5 from 'p5';

// The "sketch" function is our set of p5.js instructions.
// We'll pass this in as a prop.
const P5Sketch = ({ sketch }) => {
  // useRef gives us a way to "point" to the div we'll create.
  const sketchRef = useRef(null);

  useEffect(() => {
    // This is where the magic happens.
    // We create a new p5 instance and pass it our sketch function.
    // We also pass it the DOM node we want it to attach to.
    const p5Instance = new p5(sketch, sketchRef.current);

    // This is the "cleanup" function.
    // When the React component unmounts, this function
    // will run, telling p5 to remove the canvas.
    // This prevents memory leaks.
    return (). => {
      p5Instance.remove();
    };
  }, [sketch]); // Re-run the effect if the sketch function itself changes

  // This is the actual DOM element that React will render.
  // p5.js will attach its canvas inside this div.
  return <div ref={sketchRef} />;
};

export default P5Sketch;


Step 2: Write Your First "Sketch"

A p5.js sketch is just a function that takes one argument, typically called p (for "p5"). This p object contains all the p5.js drawing functions. The sketch must have at least a setup function and, for animations, a draw function.

Let's make a file for our sketch, MySketch.js.

JavaScript

// MySketch.js
export const sketch = (p) => {
  // `setup` runs once at the beginning
  p.setup = () => {
    // Create a 400x400 canvas.
    // p5.js will automatically attach it to the
    // element we provided in the wrapper.
    p.createCanvas(400, 400);
  };

  // `draw` runs 60 times per second (by default)
  p.draw = () => {
    // A simple pulsating circle
    p.background(10, 10, 20); // Dark blue background
    p.fill(255, 200, 0); // Bright yellow fill
    p.noStroke();

    // Use p.frameCount (current frame number) to animate
    // a sine wave for a smooth "breathing" effect.
    const diameter = 100 + p.sin(p.frameCount * 0.05) * 50;
    
    p.ellipse(p.width / 2, p.height / 2, diameter, diameter);
  };
};


Step 3: Use Your Sketch in Your App

Now, in your App.js or any other component, you can import and use your wrapper and your sketch.

JavaScript

import React from 'react';
import P5Sketch from './P5Sketch';
import { sketch } from './MySketch'; // Import the sketch function

function App() {
  return (
    <div style={{ display: 'grid', placeItems: 'center', minHeight: '100vh' }}>
      <h1>My First p5.js Sketch in React</h1>
      <P5Sketch sketch={sketch} />
    </div>
  );
}

export default App;

That's it! You now have a pulsating generative art piece running inside your React application. You can pass props to your P5Sketch wrapper to make your art interactive, responding to React's state. This combination gives you the best of both worlds: the robust component model of React and the creative, visual freedom of p5.js.

>

A close up of a control panel in a dark room

The "Feel" Factor: What Makes a UI Truly Great?

ou can build a user interface that is functionally flawless. Every button works, every form submits, and every page loads. It can be fast, accessible, and bug-free. And yet, it can feel "dead." It's a tool, not an experience. It lacks the most crucial, and most human, element of design: the "Feel" Factor.

The "Feel" is the intangible, emotional quality that separates a good product from a great one. It's the difference between an app you use and an app you love. It's the sum of a thousand tiny, deliberate design decisions that communicate quality, delight the user, and build an emotional connection.

This "feel" isn't magic. It's a specific, craftable set of design principles. When a UI "feels great," it's almost always because it has mastered these four pillars.


1. Motion with Purpose

This is the most significant pillar. Animation isn't just "fluff" or decoration; it's a powerful tool for communication. Bad animation is distracting and jarring. Great animation is invisible, guiding the user and providing feedback.

  • Feedback: The most basic role of motion. When you tap a button, it shouldn't just change. It should visually depress and pop back, like a real-world button. This tiny micro-interaction confirms the action was received. A toggle switch slides. It doesn't just teleport from "on" to "off."

  • State Transitions: How does your app move between states? When a modal window appears, does it poof into existence, or does it ease in from the center, subtly dimming the background? The latter guides the user's eye and explains where the new UI came from. List items should fade in when added, not just appear.

  • Physics & Easing: Nothing in the real world starts or stops instantly. Motion in your UI shouldn't either. Linear animations feel robotic. Using "easing" curves (like ease-out, where the animation starts fast and slows down) makes interactions feel natural and responsive. Libraries like Framer Motion are built around this idea.


2. Micro-interactions

These are the small, contained, single-purpose moments of delight. They are the reward for a user's action.

  • The Twitter "like" button doesn't just turn red. It bursts into a small particle explosion.

  • When you "pull to refresh," you don't just see a spinner. You might see a fun animation that stretches and snaps back.

  • When you complete a task in a to-do app, the item doesn't just vanish. It "crosses itself out" with a satisfying animation and fades away. These details are non-essential for function but absolutely essential for "feel." They show a level of polish and craftsmanship that builds user trust.


3. Perceived Performance

Sometimes, "feel" can make an app feel faster, even if the underlying work takes the same amount of time.

  • Skeleton Loaders: This is the most common example. Instead of showing a generic, spinning "loading" icon, you show a "skeleton" of the UI that's about to load (e.g., grey boxes where text and images will be). This does two things: it assures the user that content is coming, and it shows them the shape of the content, so the final "pop-in" is less jarring.

  • Optimistic UI: When a user clicks "like," why make them wait for the network request to finish? An optimistic UI updates the state immediately (showing the heart as "liked") while the request happens in the background. In the rare case it fails, you can seamlessly revert the change and show an error. This makes the app feel instantaneous.


4. Haptics & Sound

On mobile devices, "feel" isn't just visual. Subtle vibrations (haptics) and sounds add a tactile, physical dimension to digital interactions. A slight "thud" when you refresh a page, a crisp "click" when you flick a toggle switch, or a gentle "pop" when a notification arrives. When used sparingly, these non-visual cues make interactions feel more real and satisfying.

The "Feel" Factor is, ultimately, a form of empathy. It's the designer and developer showing the user that they cared. They didn't just build the features; they considered the experience of using them. It's the last 10% of the work that defines 90% of the user's impression of your product.

>

A close up of a control panel in a dark room

The "Feel" Factor: What Makes a UI Truly Great?

ou can build a user interface that is functionally flawless. Every button works, every form submits, and every page loads. It can be fast, accessible, and bug-free. And yet, it can feel "dead." It's a tool, not an experience. It lacks the most crucial, and most human, element of design: the "Feel" Factor.

The "Feel" is the intangible, emotional quality that separates a good product from a great one. It's the difference between an app you use and an app you love. It's the sum of a thousand tiny, deliberate design decisions that communicate quality, delight the user, and build an emotional connection.

This "feel" isn't magic. It's a specific, craftable set of design principles. When a UI "feels great," it's almost always because it has mastered these four pillars.


1. Motion with Purpose

This is the most significant pillar. Animation isn't just "fluff" or decoration; it's a powerful tool for communication. Bad animation is distracting and jarring. Great animation is invisible, guiding the user and providing feedback.

  • Feedback: The most basic role of motion. When you tap a button, it shouldn't just change. It should visually depress and pop back, like a real-world button. This tiny micro-interaction confirms the action was received. A toggle switch slides. It doesn't just teleport from "on" to "off."

  • State Transitions: How does your app move between states? When a modal window appears, does it poof into existence, or does it ease in from the center, subtly dimming the background? The latter guides the user's eye and explains where the new UI came from. List items should fade in when added, not just appear.

  • Physics & Easing: Nothing in the real world starts or stops instantly. Motion in your UI shouldn't either. Linear animations feel robotic. Using "easing" curves (like ease-out, where the animation starts fast and slows down) makes interactions feel natural and responsive. Libraries like Framer Motion are built around this idea.


2. Micro-interactions

These are the small, contained, single-purpose moments of delight. They are the reward for a user's action.

  • The Twitter "like" button doesn't just turn red. It bursts into a small particle explosion.

  • When you "pull to refresh," you don't just see a spinner. You might see a fun animation that stretches and snaps back.

  • When you complete a task in a to-do app, the item doesn't just vanish. It "crosses itself out" with a satisfying animation and fades away. These details are non-essential for function but absolutely essential for "feel." They show a level of polish and craftsmanship that builds user trust.


3. Perceived Performance

Sometimes, "feel" can make an app feel faster, even if the underlying work takes the same amount of time.

  • Skeleton Loaders: This is the most common example. Instead of showing a generic, spinning "loading" icon, you show a "skeleton" of the UI that's about to load (e.g., grey boxes where text and images will be). This does two things: it assures the user that content is coming, and it shows them the shape of the content, so the final "pop-in" is less jarring.

  • Optimistic UI: When a user clicks "like," why make them wait for the network request to finish? An optimistic UI updates the state immediately (showing the heart as "liked") while the request happens in the background. In the rare case it fails, you can seamlessly revert the change and show an error. This makes the app feel instantaneous.


4. Haptics & Sound

On mobile devices, "feel" isn't just visual. Subtle vibrations (haptics) and sounds add a tactile, physical dimension to digital interactions. A slight "thud" when you refresh a page, a crisp "click" when you flick a toggle switch, or a gentle "pop" when a notification arrives. When used sparingly, these non-visual cues make interactions feel more real and satisfying.

The "Feel" Factor is, ultimately, a form of empathy. It's the designer and developer showing the user that they cared. They didn't just build the features; they considered the experience of using them. It's the last 10% of the work that defines 90% of the user's impression of your product.

>

A close up of a control panel in a dark room

The "Feel" Factor: What Makes a UI Truly Great?

ou can build a user interface that is functionally flawless. Every button works, every form submits, and every page loads. It can be fast, accessible, and bug-free. And yet, it can feel "dead." It's a tool, not an experience. It lacks the most crucial, and most human, element of design: the "Feel" Factor.

The "Feel" is the intangible, emotional quality that separates a good product from a great one. It's the difference between an app you use and an app you love. It's the sum of a thousand tiny, deliberate design decisions that communicate quality, delight the user, and build an emotional connection.

This "feel" isn't magic. It's a specific, craftable set of design principles. When a UI "feels great," it's almost always because it has mastered these four pillars.


1. Motion with Purpose

This is the most significant pillar. Animation isn't just "fluff" or decoration; it's a powerful tool for communication. Bad animation is distracting and jarring. Great animation is invisible, guiding the user and providing feedback.

  • Feedback: The most basic role of motion. When you tap a button, it shouldn't just change. It should visually depress and pop back, like a real-world button. This tiny micro-interaction confirms the action was received. A toggle switch slides. It doesn't just teleport from "on" to "off."

  • State Transitions: How does your app move between states? When a modal window appears, does it poof into existence, or does it ease in from the center, subtly dimming the background? The latter guides the user's eye and explains where the new UI came from. List items should fade in when added, not just appear.

  • Physics & Easing: Nothing in the real world starts or stops instantly. Motion in your UI shouldn't either. Linear animations feel robotic. Using "easing" curves (like ease-out, where the animation starts fast and slows down) makes interactions feel natural and responsive. Libraries like Framer Motion are built around this idea.


2. Micro-interactions

These are the small, contained, single-purpose moments of delight. They are the reward for a user's action.

  • The Twitter "like" button doesn't just turn red. It bursts into a small particle explosion.

  • When you "pull to refresh," you don't just see a spinner. You might see a fun animation that stretches and snaps back.

  • When you complete a task in a to-do app, the item doesn't just vanish. It "crosses itself out" with a satisfying animation and fades away. These details are non-essential for function but absolutely essential for "feel." They show a level of polish and craftsmanship that builds user trust.


3. Perceived Performance

Sometimes, "feel" can make an app feel faster, even if the underlying work takes the same amount of time.

  • Skeleton Loaders: This is the most common example. Instead of showing a generic, spinning "loading" icon, you show a "skeleton" of the UI that's about to load (e.g., grey boxes where text and images will be). This does two things: it assures the user that content is coming, and it shows them the shape of the content, so the final "pop-in" is less jarring.

  • Optimistic UI: When a user clicks "like," why make them wait for the network request to finish? An optimistic UI updates the state immediately (showing the heart as "liked") while the request happens in the background. In the rare case it fails, you can seamlessly revert the change and show an error. This makes the app feel instantaneous.


4. Haptics & Sound

On mobile devices, "feel" isn't just visual. Subtle vibrations (haptics) and sounds add a tactile, physical dimension to digital interactions. A slight "thud" when you refresh a page, a crisp "click" when you flick a toggle switch, or a gentle "pop" when a notification arrives. When used sparingly, these non-visual cues make interactions feel more real and satisfying.

The "Feel" Factor is, ultimately, a form of empathy. It's the designer and developer showing the user that they cared. They didn't just build the features; they considered the experience of using them. It's the last 10% of the work that defines 90% of the user's impression of your product.

>

VISITOR NO. :

VISITOR NO :

1727

7:24:47 PM

Create a free website with Framer, the website builder loved by startups, designers and agencies.