Nextjs Tutorial


Beginners To Experts


The site is under development.

Nextjs Tutorial

What is Next.js?

Next.js is a powerful React framework that enables functionalities such as server-side rendering, static site generation, and API routes out-of-the-box. It simplifies routing, improves performance, and offers excellent developer experience.

        <!-- Next.js is not written like this in HTML but installed as a project using Node.js -->
        <!-- Example: Create a Next.js app -->
        npx create-next-app@latest my-nextjs-app
      

Benefits over React

Unlike React which is only a library for building UI, Next.js comes with routing, server-side rendering (SSR), static generation, and performance optimizations built-in.

        <!-- Benefits include: -->
        - File-based routing
        - SSR and SSG support
        - API routes
        - Improved performance with Image Optimization
        - Built-in CSS and Sass support
      

Project Structure Overview

Next.js uses a conventional folder structure. Key folders include:

        <!-- Folder structure after creating a Next.js app -->
        my-nextjs-app/
        ├── pages/          <!-- Contains all route-related React components -->
        ├── public/         <!-- Static assets like images and favicon go here -->
        ├── styles/         <!-- Global and modular CSS files -->
        ├── node_modules/   <!-- Installed packages -->
        └── next.config.js  <!-- Configuration file for Next.js -->
      

Installing Next.js

To install Next.js, Node.js must be installed first. You can then use the command below:

        <!-- Command to install Next.js -->
        npx create-next-app@latest
        <!-- or with a project name -->
        npx create-next-app@latest my-app
      

First Next.js App

Once created, you can run the development server using:

        <!-- Navigate into the directory and run -->
        cd my-app
        npm run dev
        <!-- Output -->
        > Ready on http://localhost:3000
      

Pages and Routing Basics

Each file inside the pages/ directory automatically becomes a route.

        <!-- pages/index.js becomes '/' -->
        export default function Home() {
          return <h1>Welcome to Home Page</h1>; <!-- Displayed on / -->
        }

        <!-- pages/about.js becomes '/about' -->
        export default function About() {
          return <h1>About Us Page</h1>; <!-- Displayed on /about -->
        }
      

Public vs Private Folders

The public/ folder is used for static files accessible by URL. Other files are private and not accessible directly by browser.

        <!-- Example: placing image in public folder -->
        public/logo.png

        <!-- Access in JSX -->
        <img src="/logo.png" alt="Logo" />

        <!-- Files outside public/ are not directly accessible -->
      

Linking Between Pages

Next.js provides a built-in Link component to navigate between pages without full page reloads.

        <!-- Example of linking from one page to another -->
        import Link from 'next/link';

        export default function Home() {
          return (
            <div>
              <h1>Home Page</h1>
              <Link href="/about">
                <a>Go to About Page</a>
              </Link>
            </div>
          );
        }
      

Styling with CSS

You can use global CSS or module CSS for scoped styles. Modules are recommended for component-level styles.

        <!-- styles/Home.module.css -->
        .title {
          color: blue;
          font-size: 30px;
        }

        <!-- Importing in component -->
        import styles from '../styles/Home.module.css';

        export default function Home() {
          return <h1 className={styles.title}>Styled Heading</h1>;
        }
      

Environment Setup Best Practices

Use .env.local for storing environment variables. Keep secrets out of version control.

        <!-- .env.local -->
        NEXT_PUBLIC_API_URL=https://api.example.com

        <!-- Access in code -->
        const apiUrl = process.env.NEXT_PUBLIC_API_URL;
      

File-based Routing

Next.js uses the file system to define routes. Each file inside the pages/ folder becomes a route automatically.

        <!-- pages/index.js maps to '/' -->
        export default function Home() {
          return <h1>Home Page</h1>;
        }

        <!-- pages/about.js maps to '/about' -->
        export default function About() {
          return <h1>About Page</h1>;
        }
      

Dynamic Routes

Dynamic routes are created using square brackets in the filename. Useful for user profiles, blog posts, etc.

        <!-- pages/user/[id].js handles routes like /user/1 or /user/abc -->
        import { useRouter } from 'next/router';

        export default function User() {
          const router = useRouter();
          const { id } = router.query; <!-- Access dynamic route parameter -->
          return <h1>User ID: {id}</h1>;
        }
      

Nested Routes

To create nested routes, use folders inside the pages/ directory.

        <!-- pages/blog/index.js becomes /blog -->
        <!-- pages/blog/post.js becomes /blog/post -->

        // pages/blog/index.js
        export default function Blog() {
          return <h1>Blog Home</h1>;
        }

        // pages/blog/post.js
        export default function Post() {
          return <h1>Blog Post Page</h1>;
        }
      

Catch-All Routes

Catch-all routes match multiple segments. Use three dots inside brackets.

        <!-- pages/docs/[...params].js matches /docs/a, /docs/a/b/c etc. -->
        import { useRouter } from 'next/router';

        export default function Docs() {
          const router = useRouter();
          const { params = [] } = router.query;

          return (
            <div>
              <h1>Catch-All Params:</h1>
              <ul>
                {params.map((param, i) => (
                  <li key={i}>{param}</li>
                ))}
              </ul>
            </div>
          );
        }
      

Route Groups ((group))

Route groups (folder names in double parentheses) help organize code but do not affect URL paths.

        <!-- app/((auth))/login/page.js renders on /login, not /auth/login -->
        export default function LoginPage() {
          return <h1>Login Page</h1>;
        }
      

Layout Routes (@layout.js)

Layouts can be shared across multiple pages using special @layout.js files in the app/ directory.

        <!-- app/layout.js applies to all nested pages -->
        export default function RootLayout({ children }) {
          return (
            <html>
              <body>
                <header>Site Header</header>
                {children}
                <footer>Site Footer</footer>
              </body>
            </html>
          );
        }
      

Custom 404 Page

Next.js supports custom 404 pages by creating a pages/404.js file.

        <!-- pages/404.js -->
        export default function Custom404() {
          return <h1>404 - Page Not Found</h1>;
        }
      

Middleware for Routes

Middleware allows you to run logic before rendering pages. Useful for redirects, auth checks, etc.

        <!-- middleware.js at root level -->
        import { NextResponse } from 'next/server';

        export function middleware(request) {
          const isLoggedIn = false; <!-- Replace with real auth logic -->

          if (!isLoggedIn && request.nextUrl.pathname.startsWith('/dashboard')) {
            return NextResponse.redirect(new URL('/login', request.url));
          }

          return NextResponse.next();
        }
      

Redirects and Rewrites

Configure URL redirects or rewrites in next.config.js.

        // next.config.js
        module.exports = {
          async redirects() {
            return [
              {
                source: '/old-path',
                destination: '/new-path',
                permanent: true,
              },
            ];
          },
          async rewrites() {
            return [
              {
                source: '/api/:path*',
                destination: 'https://externalapi.com/:path*',
              },
            ];
          },
        };
      

Programmatic Navigation

Use the useRouter() hook to navigate programmatically between routes.

        import { useRouter } from 'next/router';

        export default function NavigateButton() {
          const router = useRouter();

          const goToAbout = () => {
            router.push('/about'); <!-- Navigates to /about -->
          };

          return <button onClick={goToAbout}>Go to About</button>;
        }
      

Shared Layouts

Shared layouts provide consistent structure (header, footer, sidebar) across multiple pages using layout wrappers.

        // components/Layout.js
        export default function Layout({ children }) {
          return (
            <div>
              <header>My App Header</header>
              <main>{children}</main>
              <footer>My App Footer</footer>
            </div>
          );
        }

        // pages/_app.js
        import Layout from '../components/Layout';

        export default function MyApp({ Component, pageProps }) {
          return (
            <Layout>
              <Component {...pageProps} />
            </Layout>
          );
        }
      

Creating Layout Components

Layout components break down page structure into reusable parts such as headers, footers, and sidebars.

        // components/Header.js
        export default function Header() {
          return <header>Site Header</header>;
        }

        // components/Footer.js
        export default function Footer() {
          return <footer>Site Footer</footer>;
        }

        // components/Layout.js
        import Header from './Header';
        import Footer from './Footer';

        export default function Layout({ children }) {
          return (
            <>
              <Header />
              {children}
              <Footer />
            </>
          );
        }
      

Head and Meta Tags

Use the next/head component to insert page-specific metadata like titles and meta descriptions.

        import Head from 'next/head';

        export default function AboutPage() {
          return (
            <>
              <Head>
                <title>About Us</title>
                <meta name="description" content="Learn more about us." />
              </Head>
              <h1>About</h1>
            </>
          );
        }
      

Navbar and Footer

Define navigation and footer components for consistent user experience across pages.

        // components/Navbar.js
        import Link from 'next/link';

        export default function Navbar() {
          return (
            <nav>
              <Link href="/">Home</Link> | 
              <Link href="/about">About</Link>
            </nav>
          );
        }

        // components/Footer.js
        export default function Footer() {
          return <footer>© 2025 MyApp</footer>;
        }
      

Reusable UI Components

Create flexible UI elements like buttons or cards that can be reused throughout the app.

        // components/Button.js
        export default function Button({ label, onClick }) {
          return <button onClick={onClick}>{label}</button>;
        }

        // Usage
        <Button label="Click Me" onClick={() => alert('Clicked!')} />
      

Using Props

Props allow you to pass data into components from parent to child for dynamic rendering.

        // components/Greeting.js
        export default function Greeting({ name }) {
          return <p>Hello, {name}!</p>;
        }

        // Usage
        <Greeting name="Alice" />
      

Global Styles

Use global CSS or CSS Modules to style your app consistently. Define global styles in styles/globals.css.

        // styles/globals.css
        body {
          font-family: sans-serif;
          background: #f8f9fa;
        }

        // pages/_app.js
        import '../styles/globals.css';

        export default function MyApp({ Component, pageProps }) {
          return <Component {...pageProps} />;
        }
      

Dynamic Layouts per Page

You can conditionally use different layouts by adding a getLayout function per page.

        // pages/about.js
        const About = () => <h1>About Us</h1>;

        About.getLayout = function getLayout(page) {
          return (
            <div style={{ border: '2px solid blue' }}>
              <h2>Custom Layout</h2>
              {page}
            </div>
          );
        };

        export default About;

        // pages/_app.js
        export default function MyApp({ Component, pageProps }) {
          const getLayout = Component.getLayout || ((page) => page);
          return getLayout(<Component {...pageProps} />);
        }
      

Conditional Rendering

Use conditions (if, ternary, &&) to render UI based on logic such as user status or state.

        export default function Dashboard({ isLoggedIn }) {
          return (
            <div>
              {isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>}
            </div>
          );
        }
      

Theme Switcher (Light/Dark Mode)

Switch themes using context, CSS classes, or Tailwind dark mode utilities.

        // components/ThemeSwitcher.js
        import { useState } from 'react';

        export default function ThemeSwitcher() {
          const [dark, setDark] = useState(false);

          return (
            <div className={dark ? 'dark-mode' : 'light-mode'}>
              <button onClick={() => setDark(!dark)}>
                Switch to {dark ? 'Light' : 'Dark'} Mode
              </button>
              <p>Current theme: {dark ? 'Dark' : 'Light'}</p>
            </div>
          );
        }

        // styles (e.g. global.css or inline)
        .dark-mode {
          background: black;
          color: white;
        }

        .light-mode {
          background: white;
          color: black;
        }
      

Static Generation (getStaticProps)

Pre-renders the page at build time. Ideal for static content that doesn't change often.

        // pages/posts.js
        export async function getStaticProps() {
          const res = await fetch('https://jsonplaceholder.typicode.com/posts');
          const posts = await res.json();

          return {
            props: { posts },
          };
        }

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

Server-side Rendering (getServerSideProps)

Fetches data on every request. Good for dynamic data that changes frequently.

        // pages/news.js
        export async function getServerSideProps() {
          const res = await fetch('https://api.example.com/news');
          const news = await res.json();

          return {
            props: { news },
          };
        }

        export default function News({ news }) {
          return (
            <div>
              {news.map(item => (
                <p key={item.id}>{item.title}</p>
              ))}
            </div>
          );
        }
      

Incremental Static Regeneration (ISR)

Update static pages after build time by specifying a revalidate interval.

        // pages/blog.js
        export async function getStaticProps() {
          const res = await fetch('https://api.example.com/blog');
          const data = await res.json();

          return {
            props: { data },
            revalidate: 10, // Regenerate page every 10 seconds
          };
        }

        export default function Blog({ data }) {
          return (
            <div>
              {data.map(post => (
                <h2 key={post.id}>{post.title}</h2>
              ))}
            </div>
          );
        }
      

Client-side Fetching

Use useEffect and useState to fetch data after the component mounts.

        import { useEffect, useState } from 'react';

        export default function Users() {
          const [users, setUsers] = useState([]);

          useEffect(() => {
            fetch('https://jsonplaceholder.typicode.com/users')
              .then(res => res.json())
              .then(data => setUsers(data));
          }, []);

          return (
            <ul>
              {users.map(user => (
                <li key={user.id}>{user.name}</li>
              ))}
            </ul>
          );
        }
      

Fetching from REST API

Call external RESTful endpoints using fetch, axios, etc.

        import { useEffect, useState } from 'react';
        import axios from 'axios';

        export default function Products() {
          const [products, setProducts] = useState([]);

          useEffect(() => {
            axios.get('https://fakestoreapi.com/products')
              .then(res => setProducts(res.data));
          }, []);

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

Fetching from GraphQL API

Use libraries like Apollo Client or simple fetch to query GraphQL APIs.

        // Sample GraphQL Query using fetch
        const query = `
          {
            countries {
              code
              name
            }
          }
        `;

        export default function GraphQLExample() {
          const [data, setData] = useState([]);

          useEffect(() => {
            fetch('https://countries.trevorblades.com/', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ query }),
            })
              .then(res => res.json())
              .then(result => setData(result.data.countries));
          }, []);

          return (
            <ul>
              {data.map(c => (
                <li key={c.code}>{c.name}</li>
              ))}
            </ul>
          );
        }
      

SWR (Stale-While-Revalidate)

A React hook for fetching data. Returns cached data first and revalidates in background.

        import useSWR from 'swr';

        const fetcher = url => fetch(url).then(res => res.json());

        export default function Profile() {
          const { data, error } = useSWR('/api/user', fetcher);

          if (error) return <div>Failed to load</div>;
          if (!data) return <div>Loading...</div>;

          return <div>Hello, {data.name}</div>;
        }
      

React Query in Next.js

React Query helps with caching, pagination, background refetching, etc.

        import { useQuery } from '@tanstack/react-query';

        const fetchUsers = async () => {
          const res = await fetch('https://jsonplaceholder.typicode.com/users');
          return res.json();
        };

        export default function Users() {
          const { data, isLoading, error } = useQuery({
            queryKey: ['users'],
            queryFn: fetchUsers,
          });

          if (isLoading) return <div>Loading...</div>;
          if (error) return <div>Error!</div>;

          return (
            <ul>
              {data.map(u => (
                <li key={u.id}>{u.name}</li>
              ))}
            </ul>
          );
        }
      

Caching Strategies

Control caching behavior with HTTP headers, SWR, React Query, or ISR. Choose between stale-while-revalidate, client memory, or API-side caching.

        // Example: ISR with cache-time of 30 seconds
        export async function getStaticProps() {
          const res = await fetch('https://api.example.com/data');
          const data = await res.json();

          return {
            props: { data },
            revalidate: 30,
          };
        }
      

Error Handling in Fetching

Always handle loading, error, and empty states in your fetch logic for a better user experience.

        import { useEffect, useState } from 'react';

        export default function SafeFetch() {
          const [data, setData] = useState(null);
          const [loading, setLoading] = useState(true);
          const [error, setError] = useState(null);

          useEffect(() => {
            fetch('https://api.example.com/data')
              .then(res => {
                if (!res.ok) throw new Error('Network error');
                return res.json();
              })
              .then(data => {
                setData(data);
                setLoading(false);
              })
              .catch(err => {
                setError(err.message);
                setLoading(false);
              });
          }, []);

          if (loading) return <p>Loading...</p>;
          if (error) return <p>Error: {error}</p>;

          return <pre>{JSON.stringify(data, null, 2)}</pre>;
        }
      

Creating API Endpoints

API routes are created as files inside pages/api.
Each file exports a default function with (req, res) parameters.

// pages/api/hello.js
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello from API!' });
}


Request and Response Object

req contains request data like method, headers, body, query.
res is used to send back status, headers, and body.

// pages/api/method.js
export default function handler(req, res) {
  if(req.method === 'POST') {
    res.status(200).json({ message: 'POST received' });
  } else {
    res.status(405).json({ error: 'Only POST allowed' });
  }
}


Routing in pages/api

The folder/file structure inside pages/api defines the API routes.
For example, pages/api/user.js corresponds to /api/user.

Dynamic API Routes

Use square brackets for dynamic segments in filenames.
// pages/api/user/[id].js
export default function handler(req, res) {
  const { id } = req.query;
  res.status(200).json({ userId: id });
}


Handling POST/GET

Check req.method to handle different HTTP methods.
// pages/api/data.js
export default function handler(req, res) {
  if(req.method === 'GET') {
    res.status(200).json({ data: 'GET response' });
  } else if(req.method === 'POST') {
    const body = req.body;
    res.status(200).json({ received: body });
  } else {
    res.status(405).end();
  }
}


Custom Headers

Set or read HTTP headers with req.headers and res.setHeader().
// pages/api/header.js
export default function handler(req, res) {
  const token = req.headers['authorization'];
  res.setHeader('X-Custom-Header', 'MyHeaderValue');
  res.status(200).json({ token });
}


Rate Limiting

Use libraries or custom logic to limit requests.
Example: check IP and timestamp to block excessive calls.

Connecting to a Database

API routes can connect to databases like MongoDB, PostgreSQL.
Connect inside the handler function or use a helper.

// pages/api/db.js (pseudo-code)
import client from '../../lib/dbClient';
export default async function handler(req, res) {
  const users = await client.query('SELECT * FROM users');
  res.status(200).json(users.rows);
}


Using Third-party APIs

You can call external APIs inside your API route using fetch or axios.

// pages/api/weather.js
import fetch from 'node-fetch';
export default async function handler(req, res) {
  const response = await fetch('https://api.weatherapi.com/v1/current.json?key=KEY&q=London');
  const data = await response.json();
  res.status(200).json(data);
}


Authentication in APIs

Use tokens or sessions to authenticate requests.
Check headers or cookies inside your handler.

// pages/api/secure.js
export default function handler(req, res) {
  const auth = req.headers.authorization;
  if(auth === 'Bearer mysecrettoken') {
    res.status(200).json({ secretData: '42' });
  } else {
    res.status(401).json({ error: 'Unauthorized' });
  }
}


Recap

  • API routes live in pages/api as serverless functions.
  • Handle requests and responses with req and res.
  • Support dynamic routing, HTTP methods, headers, and authentication.
  • Connect to databases and third-party services easily.

Global CSS

Global CSS affects the entire app and is imported in pages/_app.js.

<!-- styles/globals.css -->
body {
  background-color: #f0f0f0;
  font-family: Arial, sans-serif;
}
<!-- pages/_app.js -->
import '../styles/globals.css';
export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}
Output:
The entire app has a light gray background and Arial font.

CSS Modules

CSS Modules scope styles locally by default.
Filename: Component.module.css

<!-- styles/Button.module.css -->
.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
}
<!-- components/Button.js -->
import styles from '../styles/Button.module.css';
export default function Button() {
  return <button className={styles.button}>Click Me</button>;
}
Output:
A blue button with white text styled locally.

Tailwind CSS Integration

Tailwind is a utility-first CSS framework.
Install and configure it in Next.js.

<!-- Example usage in a component -->
export default function Card() {
  return <div className="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4">
    <div>
      <h1 className="text-xl font-medium text-black">Tailwind Card</h1>
      <p className="text-gray-500">Utility-first styling!</p>
    </div>
  </div>
}
Output:
A nicely spaced card with padding, shadow, and rounded corners.

Styled Components

CSS-in-JS library for scoped styles.
Install and use styled-components.

import styled from 'styled-components';
const Button = styled.button`
  background: palevioletred;
  color: white;
  padding: 10px 20px;
  border-radius: 5px;
`;
export default function StyledButton() {
  return <Button>Styled Components</Button>;
}
Output:
A pinkish button styled with styled-components.

Emotion CSS-in-JS

Similar to styled-components, Emotion allows styling via JavaScript.

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
const style = css`
  background-color: lightblue;
  padding: 10px;
  border-radius: 5px;
`;
export default function EmotionBox() {
  return <div css={style}>Emotion Styled Box</div>;
}
Output:
A light blue box styled with Emotion.

SCSS / SASS Support

Next.js supports SASS/SCSS out of the box.
Use files like styles.module.scss.

<!-- styles/Button.module.scss -->
$primary-color: #0070f3;
.button {
  background-color: $primary-color;
  color: white;
  padding: 10px 20px;
}
Output:
Button styled using SCSS variables and nesting.

Responsive Design

Use CSS media queries or frameworks like Tailwind.

<!-- Example responsive style in CSS Modules -->
.container {
  width: 100%;
  }@media (min-width: 768px) {
    width: 50%;
  }
}
Output:
Container width changes based on screen size.

Animations with Framer Motion

Framer Motion adds animations easily.

import { motion } from 'framer-motion';
export default function AnimatedBox() {
  return <motion.div
    initial={{ opacity: 0 }}
    animate={{ opacity: 1 }}
    transition={{ duration: 1 }}
    style={{ width: 100, height: 100, backgroundColor: 'red' }}
  ></motion.div>
}
Output:
A red box fades in on mount.

Theming System

Implement theme switching using React context or CSS variables.

const themes = {
  light: { background: '#fff', color: '#000' },
  dark: { background: '#000', color: '#fff' },
};
export default function ThemeSwitcher() {
  const [theme, setTheme] = React.useState('light');
  return <div style={{ background: themes[theme].background, color: themes[theme].color, padding: '20px' }}>
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button>
    Current theme: {theme}
  </div>
}
Output:
Area background and text color toggle between light and dark.

Utility-First CSS vs Component Styles

Utility-first CSS (Tailwind) uses small reusable classes.
Component styles use scoped or CSS-in-JS styles.
Choose based on your project needs.

Local State with useState

Manage state inside components with React's useState hook.

import React, { useState } from 'react';
export default function Counter() {
let [count, setCount] = useState(0);
return (<
button onClick={() => setCount(count + 1)}>Clicked {count} times</button>
);
}
Output:
Button increments and displays the count.

Global State with Context API

Share state across components using React Context.

import React, { createContext, useContext, useState } from 'react';
const CountContext = createContext();
export function CountProvider({ children }) {
let [count, setCount] = useState(0);
return (<CountContext.Provider value={{ count, setCount }}>{children}</CountContext.Provider>);
}
export function useCount() { return useContext(CountContext); }
Output:
Components wrapped in CountProvider can access and update count.

Zustand for Global State

Lightweight state library for React.

import create from 'zustand';
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 }))
}));
export default function Counter() {
let count = useStore(state => state.count);
let increment = useStore(state => state.increment);
return (<button onClick={increment}>Count: {count}</button>);
}
Output:
Button updates count using Zustand global state.

Redux Toolkit Integration

Use Redux Toolkit to manage complex global state.

import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: { increment: state => state + 1 }
});
const store = configureStore({ reducer: counterSlice.reducer });
export default store;
Output:
Store with a simple increment reducer setup.

React Query for Server State

Manage server state with caching and updates.

import { useQuery } from 'react-query';
function fetchTodos() { return fetch('/api/todos').then(res => res.json()); }
export default function Todos() {
let { data, error, isLoading } = useQuery('todos', fetchTodos);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
return (<ul>{data.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>);
}
Output:
Fetches and displays todos with loading and error states.

Form State Management

Manage controlled inputs and form data in React.

import React, { useState } from 'react';
export default function Form() {
let [name, setName] = useState('');
return (<
form onSubmit={e => { e.preventDefault(); alert(name); }}>
input value={name} onChange={e => setName(e.target.value)} placeholder="Enter name" />
button type="submit">Submit</button>
/form>
);
}
Output:
Controlled input updates state and alerts on submit.

Persisting State in LocalStorage

Save state between reloads using LocalStorage.

import React, { useState, useEffect } from 'react';
export default function PersistentCounter() {
let [count, setCount] = useState(() => parseInt(localStorage.getItem('count')) || 0);
useEffect(() => { localStorage.setItem('count', count); }, [count]);
return (<
button onClick={() => setCount(count + 1)}>Count: {count}</button>
);
}
Output:
Count persists across page reloads.

Shared State Across Pages

Use global state libraries or Context to share state.

import { useContext } from 'react';
import { CountContext } from '../context/CountContext';
export default function Page() {
let { count, setCount } = useContext(CountContext);
return (<
button onClick={() => setCount(count + 1)}>Shared Count: {count}</button>
);
}
Output:
State is accessible and modifiable from any page.

Debounced State Updates

Limit frequency of state updates to improve performance.

import React, { useState, useEffect } from 'react';
function useDebounce(value, delay) {
let [debounced, setDebounced] = useState(value);
useEffect(() => {
let handler = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debounced;
}
export default function Search() {
let [query, setQuery] = useState('');
let debouncedQuery = useDebounce(query, 500);
// Use debouncedQuery to fetch API
return (<
input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search" />
);
}
Output:
Input updates delayed by 500ms to avoid too many updates.

Middleware-Enhanced State

Enhance state management with middleware (e.g., logging).

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
const loggerMiddleware = store => next => action => {
console.log('Dispatching:', action);
let result = next(action);
console.log('Next state:', store.getState());
return result;
};
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware().concat(loggerMiddleware)
});
export default store;
Output:
Logs actions and state changes to the console.

Controlled vs Uncontrolled Forms

Controlled forms have input values controlled by React state, uncontrolled forms use refs.

/* Controlled input example */
function ControlledInput() {
const [value, setValue] = React.useState('');
return (<
input value={value} onChange={e => setValue(e.target.value)} />
);
}
Output:
Input value updates React state on every keystroke.

/* Uncontrolled input example */
function UncontrolledInput() {
const inputRef = React.useRef(null);
const handleClick = () => alert(inputRef.current.value);
return (<
>input ref={inputRef} />
button onClick={handleClick}>Show Value</button>
);
}
Output:
Input value accessed via ref on button click.

useState for Forms

Manage form input values with useState for controlled components.

function Form() {
const [formData, setFormData] = React.useState({ name: '', email: '' });
const handleChange = e => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
return (<
form>
input name="name" value={formData.name} onChange={handleChange} />
input name="email" value={formData.email} onChange={handleChange} />
/form>
);
}
Output:
Form inputs update their respective state fields.

useRef for Form Inputs

Access form values directly without React state.

function FormWithRef() {
const nameRef = React.useRef();
const emailRef = React.useRef();
const handleSubmit = e => {
e.preventDefault();
alert(`Name: ${nameRef.current.value}, Email: ${emailRef.current.value}`);
};
return (<
form onSubmit={handleSubmit}>
input ref={nameRef} name="name" />
input ref={emailRef} name="email" />
button type="submit">Submit</button>
/form>
);
}
Output:
Form submission alerts values accessed via refs.

React Hook Form Setup

Use React Hook Form library to simplify form state and validation.

import { useForm } from 'react-hook-form';
function HookForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = data => console.log(data);
return (<
form onSubmit={handleSubmit(onSubmit)}>
input {...register('name', { required: true })} />
{errors.name && <span>Name is required</span>}
button type="submit">Submit</button>
/form>
);
}
Output:
Validates required name field and logs data on submit.

Form Validation with Yup

Schema-based validation with Yup library.

import * as yup from 'yup';
const schema = yup.object().shape({
name: yup.string().required(),
email: yup.string().email().required()
});
// Integrate with React Hook Form using yupResolver
Output:
Defines validation schema to enforce form rules.

Custom Validation

Write custom validation functions.

function validateEmail(email) {
return email.includes('@');
}
function Form() {
const [email, setEmail] = React.useState('');
const [error, setError] = React.useState(null);
const handleChange = e => {
setEmail(e.target.value);
setError(validateEmail(e.target.value) ? null : 'Invalid email');
};
return (<
>input value={email} onChange={handleChange} />
{error && <span>{error}</span>}
);
}
Output:
Validates email and shows error message.

Multi-Step Forms

Split forms into multiple steps/pages.

function MultiStep() {
const [step, setStep] = React.useState(1);
return (<
div>
{step === 1 ? <Step1 next={() => setStep(2)} /> : <Step2 prev={() => setStep(1)} />}
/div>
);
}
Output:
User navigates through form steps.

File Upload Forms

Handle file input in forms.

function FileUpload() {
const [file, setFile] = React.useState(null);
const handleChange = e => setFile(e.target.files[0]);
return (<
input type="file" onChange={handleChange} />
{file && <p>Selected: {file.name}</p>}
);
}
Output:
Displays selected file name.

Form Submission to API

Send form data to backend API.

function SubmitForm() {
const [name, setName] = React.useState('');
const handleSubmit = e => {
e.preventDefault();
fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
});
};
return (<
form onSubmit={handleSubmit}>
input value={name} onChange={e => setName(e.target.value)} />
button type="submit">Submit</button>
/form>
);
}
Output:
Sends JSON data to API on form submission.

UX Tips for Forms

Improve usability with feedback, focus, and validation.
Examples include:
- Clear error messages
- Highlight invalid inputs
- Disable submit button while processing

Understanding Auth Flows

Authentication verifies who a user is, authorization determines what they can access.

NextAuth.js Overview

A popular library for authentication in Next.js, supporting multiple providers.

import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
});
Output:
Sets up Google OAuth with NextAuth.js.

Social Logins (Google, GitHub)

Use OAuth providers to enable login via external services.

Credentials-based Auth

Authenticate users with email/password via API.

import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
export default NextAuth({
providers: [
CredentialsProvider({
async authorize(credentials) {
const user = await checkUser(credentials.email, credentials.password);
if (user) {
return user;
} else {
return null;
}
},
}),
],
});
Output:
Authorizes user with custom logic.

Protecting API Routes

Use middleware or session checks to guard API endpoints.

import { getSession } from 'next-auth/react';
export default async (req, res) => {
const session = await getSession({ req });
if (!session) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.status(200).json({ message: 'Authorized access' });
};
Output:
API route returns 401 if user is not authenticated.

Session Management

Manage user sessions client and server side for persistent login.

Role-based Access Control

Assign roles to users to restrict access to resources.

if (session.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
Output:
Blocks access to users without admin role.

Client vs Server Auth

Client side: React hooks like useSession
Server side: getServerSideProps or API middleware.

JWT Handling

Use JWT tokens for stateless authentication and authorization.

import jwt from 'jsonwebtoken';
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
const decoded = jwt.verify(token, process.env.JWT_SECRET);
Output:
Creates and verifies JWT token.

Custom Auth Implementation

Build your own auth logic if needed.

Vercel Deployment

Vercel is the official platform for deploying Next.js apps with zero config.

# Deploy your Next.js app with Vercel CLI
vercel login
vercel --prod
Output:
Deploys your app to Vercel production environment.

Static vs Server-rendered Deploy

Static: pre-generated pages served from CDN.
Server-rendered: pages rendered on-demand per request.

Custom Domains

Add your own domain to your Vercel project via dashboard.

Environment Variables

Securely store API keys and secrets for builds and runtime.

# In .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
# Access in code
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
Output:
Environment variables available in your app.

Vercel Analytics

Built-in analytics to monitor site performance and traffic.

Deploying to Netlify

Next.js can also deploy to Netlify using adapter or build plugins.

# Install Next.js plugin for Netlify
npm install @netlify/plugin-nextjs
# Add to netlify.toml
[[plugins]]
package = "@netlify/plugin-nextjs"
Output:
Enables Next.js support on Netlify builds.

Using Docker with Next.js

Containerize your app for consistent deployment environments.

# Dockerfile example
FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
RUN yarn build
EXPOSE 3000
CMD ["yarn", "start"]
Output:
Docker container ready to run Next.js app.

CDN Caching

Use CDNs to cache static assets globally for fast load times.

Preview Deployments

Deploy branches or PRs automatically for testing.

CI/CD Integration

Integrate with GitHub Actions, GitLab CI, or other pipelines.

# GitHub Actions example snippet
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: yarn install
- name: Build
run: yarn build
- name: Deploy to Vercel
run: vercel --prod --token ${{ secrets.VERCEL_TOKEN }}
Output:
Automates build and deploy on push.

Using next/head

Use Next.js' next/head to customize the document head per page.

import Head from 'next/head';
export default function Home() {
return (
<div>
<Head>
<title>My Awesome Page</title>
<meta name="description" content="Welcome to my awesome page." />
</Head>
<h1>Hello World</h1>
</div>
);
}
Output:
Sets the page title and meta description dynamically.

Open Graph Meta Tags

Improve social sharing by adding Open Graph tags.

<Head>
<meta property="og:title" content="My Awesome Page" />
<meta property="og:description" content="Welcome to my awesome page." />
<meta property="og:image" content="https://example.com/image.png" />
</Head>
Output:
Enables rich link previews on social media.

Structured Data (Schema.org)

Add JSON-LD scripts to help search engines understand your content.

<Head>
<script type="application/ld+json">{`
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "My Company",
"url": "https://mycompany.com",
"logo": "https://mycompany.com/logo.png"
}
`}</script>
</Head>
Output:
Helps SEO with rich snippets.

Dynamic SEO Content

Change meta tags based on dynamic data or route parameters.

Robots.txt & Sitemap

Use static files or generate automatically to guide search engines.

# public/robots.txt
User-agent: *
Disallow:
# public/sitemap.xml
Output:
Controls crawler behavior and sitemap indexing.

Canonical Tags

Prevent duplicate content issues by specifying canonical URLs.

<Head>
<link rel="canonical" href="https://example.com/current-page" />
</Head>
Output:
Signals preferred URL to search engines.

Lighthouse SEO Tips

Use Google Lighthouse to audit and improve SEO performance.

Social Sharing Optimization

Customize meta tags for Facebook, Twitter, LinkedIn previews.

Avoiding Duplicate Content

Use canonical tags and consistent URL structure.

Performance & SEO

Optimize images, use lazy loading, and minimize JavaScript for better SEO.

next/image Component

Next.js provides the next/image component for automatic image optimization.

import Image from 'next/image';
export default function MyImage() {
return (
<Image
src="/me.png"
alt="Picture of me"
width={500}
height={500}
/>
);
}
Output:
Optimized image with automatic resizing and lazy loading.

Responsive Images

The sizes and layout props help serve images that fit different screen sizes.

<Image
src="/photo.jpg"
alt="Photo"
layout="responsive"
width={700}
height={475}
/>
Output:
Image scales responsively while maintaining aspect ratio.

Lazy Loading

Images are lazy-loaded by default to improve page load performance.

External Image Domains

Configure next.config.js to allow loading images from external sources.

module.exports = {
images: {
domains: ['example.com'],
},
};
Output:
Enables loading images hosted on example.com.

Image Caching

Images are cached at the CDN level to speed up repeat loads.

Blur Placeholders

Show a blurred, low-res placeholder while the full image loads.

<Image
src="/photo.jpg"
alt="Photo"
width={700}
height={475}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
/>
Output:
Displays a blurry preview before the image fully loads.

AVIF and WebP Support

Next.js automatically serves modern formats like AVIF and WebP when supported.

CDN-backed Images

Use Vercel's or your own CDN to serve images globally for faster delivery.

SVG Handling

SVGs can be imported as components or used as image sources.

Optimization Strategies

Combine resizing, caching, lazy loading, and format selection for best results.

Lazy Loading Components

Lazy loading delays loading components until they are needed to improve initial load speed.

import dynamic from 'next/dynamic';
const LazyComponent = dynamic(() => import('./LazyComponent'));
export default function Page() {
return <LazyComponent />;
}
Output:
Component loads only when rendered, reducing initial bundle size.

Dynamic Imports

Dynamically import modules to split code.

const SomeModule = dynamic(() => import('./SomeModule'));
export default function Home() {
return <SomeModule />;
}
Output:
Loads the module only when needed.

Code Splitting

Automatically splits code by page to improve performance.

Bundle Analyzer

Use the bundle analyzer tool to identify large dependencies.

npm install @next/bundle-analyzer
# then add config in next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
Output:
Visualizes your bundle size.

Image Optimization

Use the next/image component for optimized images.

Preloading Critical Resources

Use <link rel="preload"> to load important resources early.

Reducing JavaScript Payload

Remove unused libraries and code to shrink JavaScript size.

Avoiding Unused CSS

Use tools like PurgeCSS to remove unused CSS.

Lighthouse Auditing

Run Lighthouse audits to get performance metrics and suggestions.

Web Vitals Monitoring

Monitor Core Web Vitals to track real user performance.

What is Middleware?

Middleware is code that runs before a request is completed, allowing you to modify the response or perform logic.

export function middleware(request) {
// runs before every request
return NextResponse.next();
}
Output:
Middleware intercepts requests to run logic or modify responses.

Middleware for Authentication

Middleware can check authentication before allowing access.

import { NextResponse } from 'next/server';
export function middleware(request) {
const token = request.cookies.get('token');
if (!token) {
return NextResponse.redirect('/login');
}
return NextResponse.next();
}
Output:
Redirects users to login if no auth token.

Redirect Logic

Middleware can redirect based on request conditions.

Middleware in middleware.ts

Place middleware in the root as middleware.ts to apply globally.

Using Cookies in Middleware

Cookies can be read and modified within middleware.

const cookie = request.cookies.get('session');
// use or set cookies
Output:
Access cookies for auth or personalization.

Edge API Routes

Edge functions run closer to the user for faster responses.

Streaming Responses

Middleware can stream data in chunks for better performance.

Third-party Logging

Send logs from middleware to external services.

Rate Limiting with Middleware

Implement request rate limiting to prevent abuse.

Geo-based Middleware

Use IP/geolocation data to customize responses.

Next.js i18n Setup

Next.js supports internationalization by configuring next.config.js.

module.exports = {
i18n: {
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
},
};
Output:
Next.js will serve pages in English, French, and German.

Language Switching

You can switch languages programmatically or via UI.

import { useRouter } from 'next/router';
const router = useRouter();
const changeToFrench = () => {
router.push(router.pathname, router.asPath, { locale: 'fr' });
};
Output:
Changes the site language to French.

Localized Routing

Routes are automatically prefixed with locale codes.

Static Translations

Use JSON files to provide static text for each language.

/locales/en/common.json
{
"welcome": "Welcome",
"hello": "Hello World"
}
Output:
Loads static translations from JSON files.

Dynamic Translations

Load translations dynamically at runtime for flexibility.

Detecting User Language

Detect browser language or use headers to set locale.

Date and Number Formatting

Use JavaScript Intl API to format dates and numbers per locale.

const date = new Date();
const formattedDate = new Intl.DateTimeFormat('fr-FR').format(date);
console.log(formattedDate);
Output:
Formats date in French style.

Multi-language SEO

Configure hreflang tags for SEO benefits.

Using i18next

Integrate the i18next library for advanced i18n features.

External CMS for Translations

Manage translations with external services like Contentful or Sanity.

What is Headless CMS?

A Headless CMS is a backend content management system that delivers content via APIs without a frontend.

Integrating with Contentful

Use Contentful’s SDK to fetch content.

import { createClient } from 'contentful';
const client = createClient({
space: 'your_space_id',
accessToken: 'your_access_token',
});
export async function getStaticProps() {
const entries = await client.getEntries();
return { props: { entries: entries.items } };
}
Output:
Fetches entries from Contentful for static generation.

Integrating with Sanity.io

Use Sanity client to query content.

import sanityClient from '@sanity/client';
const client = sanityClient({
projectId: 'your_project_id',
dataset: 'production',
useCdn: true,
});
export async function getStaticProps() {
const data = await client.fetch('*[_type == "post"]');
return { props: { posts: data } };
}
Output:
Retrieves posts from Sanity CMS.

Integrating with Strapi

Fetch data via Strapi REST or GraphQL APIs.

export async function getStaticProps() {
const res = await fetch('https://your-strapi-url/api/posts');
const data = await res.json();
return { props: { posts: data.data } };
}
Output:
Retrieves posts from Strapi CMS.

Using Notion as CMS

Use Notion API to manage content.

Markdown Content with MDX

Use MDX to write markdown with React components.

import { MDXRemote } from 'next-mdx-remote';
import { serialize } from 'next-mdx-remote/serialize';
export async function getStaticProps() {
const mdxSource = await serialize('# Hello MDX');
return { props: { source: mdxSource } };
}
Output:
Renders markdown content with React components.

Fetching Blog Content

Fetch posts from CMS during build or runtime.

Real-time Preview

Enable live preview of content changes.

Revalidating CMS Content

Use ISR to revalidate content.

export async function getStaticProps() {
const posts = await fetchCMSPosts();
return {
props: { posts },
revalidate: 10, // Revalidate every 10 seconds
};
}
Output:
Next.js regenerates the page periodically.

Best Practices with CMS

Use caching, secure tokens, and proper error handling.

Creating Product Pages

Use dynamic routes to create product detail pages based on product IDs.

export async function getStaticPaths() {
const products = await fetchProducts();
const paths = products.map(product => ({
params: { id: product.id.toString() },
}));
return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
const product = await fetchProductById(params.id);
return { props: { product } };
}
Output:
Generates static pages for each product.

Cart State Management

Manage cart state using React Context or Zustand for global state.

import { createContext, useState, useContext } from 'react';
const CartContext = createContext();
export function CartProvider({ children }) {
const [cart, setCart] = useState([]);
const addToCart = (item) => setCart([...cart, item]);
return <CartContext.Provider value={{ cart, addToCart }}>{children}</CartContext.Provider>;
}
export const useCart = () => useContext(CartContext);
Output:
Provides cart state globally across pages.

Checkout Flow

Implement multi-step checkout with forms to collect user info.

Stripe Payment Integration

Use Stripe’s API and React Stripe.js for secure payments.

import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe('your-publishable-key');
async function handleCheckout() {
const stripe = await stripePromise;
const { error } = await stripe.redirectToCheckout({
sessionId: 'your-session-id',
});
if (error) console.error(error);
}
Output:
Redirects user to Stripe checkout page.

Inventory Management

Track stock levels and update after purchases.

Order API Routes

Create Next.js API routes to handle order creation and status.

export default async function handler(req, res) {
if (req.method === 'POST') {
const orderData = req.body;
// Save orderData to database
res.status(200).json({ message: 'Order created' });
} else {
res.status(405).end();
}
}
Output:
API endpoint for creating orders.

Product Filtering & Search

Use query parameters and client-side filtering for product lists.

Admin Dashboard

Build protected pages for managing products and orders.

Email Order Confirmations

Integrate with services like SendGrid or Nodemailer to email customers.

Webhooks with Stripe

Handle Stripe events to update order status automatically.

export default async function webhookHandler(req, res) {
const event = req.body;
switch(event.type) {
case 'checkout.session.completed':
// Fulfill order logic
break;
default:
console.log(`Unhandled event type ${event.type}`);
}
res.status(200).json({ received: true });
}
Output:
Processes Stripe webhook events.

What is a Monorepo?

A monorepo is a single repository that holds multiple projects or packages, making it easier to share code and manage dependencies across related projects.

Using Turborepo

Turborepo is a high-performance build system for JavaScript and TypeScript monorepos.

npx create-turbo@latest
cd my-monorepo
npm install
npm run dev
Output:
Sets up and runs a Turborepo monorepo.

Shared Component Libraries

Create reusable UI components that can be imported across multiple apps inside the monorepo.

Micro-frontend Basics

Micro-frontends break a frontend app into smaller, independently deployable pieces, often built by different teams.

Nx in Next.js

Nx provides tooling for managing monorepos with support for Next.js, enabling powerful code sharing and tooling.

Code Splitting in Monorepos

Code splitting allows loading only the required code chunks, improving performance.

Environment Management

Manage environment variables and settings independently for each app/package in the monorepo.

Isolated Builds

Build only affected projects to speed up CI/CD workflows.

Module Federation

Module Federation allows sharing modules dynamically between separate builds at runtime.

Deployment Strategies

Deploy micro-frontends separately or as a unified app depending on your infrastructure.

Unit Testing with Jest

Jest is a popular JavaScript testing framework used for writing unit tests.

npm install --save-dev jest

// example.test.js
describe('sum function', () => {
  test('adds 1 + 2 to equal 3', () => {
    expect(1 + 2).toBe(3);
  });
});
Output:
Jest runs the test and verifies the sum.

React Testing Library

This library helps test React components by simulating user interactions.

npm install --save-dev @testing-library/react

import { render, screen } from '@testing-library/react';
import Home from '../pages/index';

test('renders welcome message', () => {
  render(<Home />);
  expect(screen.getByText('Welcome')).toBeInTheDocument();
});
Output:
Tests if the "Welcome" text is rendered.

Writing Testable Components

Design components to accept props and use hooks to enable easier testing.

Snapshot Testing

Captures the rendered component’s output and compares it to a saved snapshot to detect changes.

import renderer from 'react-test-renderer';
import Button from '../components/Button';

test('Button snapshot', () => {
  const tree = renderer.create(<Button label="Click me" />).toJSON();
  expect(tree).toMatchSnapshot();
});
Output:
Saves snapshot and compares on future runs.

Mocking API Calls

Use Jest to mock fetch or axios calls to isolate tests.

End-to-End Testing with Cypress

Cypress automates browser tests for full app flows.

npm install cypress --save-dev

// In cypress/integration/sample_spec.js
describe('My First Test', () => {
  it('Visits the Next.js app', () => {
    cy.visit('http://localhost:3000');
    cy.contains('Welcome');
  });
});
Output:
Cypress opens the browser and runs the test.

Test Coverage Reports

Generate reports to check how much code is tested.

CI Testing Setup

Integrate tests in Continuous Integration pipelines for automated testing.

Storybook for Visual Testing

Storybook allows visual development and testing of UI components.

Test-Driven Development (TDD)

Write tests before coding features to ensure better quality and design.

AI-Powered Frontends

Intro to AI in Frontend


function greetUser(name) {
return `Hello, ${name}! How can AI assist you today?`;
}
console.log(greetUser("Alice")); // > Hello, Alice! How can AI assist you today?

AI UX Principles


const aiUX = {
transparency: "Explain AI decisions",
control: "Allow user override",
seamless: "Integrate AI naturally"
};
console.log(aiUX);

Integrating Chatbots


function chatbotReply(message) {
if(message.toLowerCase().includes("hello")) {
return "Hi there! How can I help you?";
} else {
return "Sorry, I didn't get that.";
}
}
console.log(chatbotReply("Hello")); // > Hi there! How can I help you?

Voice Assistant UI


function listenVoiceCommand(command) {
if(command === "play music") {
return "Playing music...";
} else {
return "Command not recognized.";
}
}
console.log(listenVoiceCommand("play music")); // > Playing music...

Text Summarization UIs


function summarize(text) {
return text.split(".")[0] + ".";
}
console.log(summarize("AI helps make interfaces smarter. It saves time.")); // > AI helps make interfaces smarter.

AI-Powered Recommendations


function recommend(items, interest) {
return items.filter(item => item.includes(interest));
}
console.log(recommend(["apple", "banana", "apricot"], "ap")); // > ["apple", "apricot"]

AI in Forms (Auto-fill)


const userData = { email: "alice@example.com" };
function autofill(field) {
return userData[field] || "";
}
console.log(autofill("email")); // > alice@example.com

Real-time Language Translation


const dictionary = { hello: "hola", goodbye: "adios" };
function translate(word) {
return dictionary[word.toLowerCase()] || "unknown";
}
console.log(translate("Hello")); // > hola

Predictive Typing


function predict(prefix, dictionary) {
return dictionary.filter(word => word.startsWith(prefix));
}
console.log(predict("ap", ["apple", "apricot", "banana"])); // > ["apple", "apricot"]

Sentiment Analysis in UI


function sentiment(text) {
const positiveWords = ["good", "happy", "great"];
const negativeWords = ["bad", "sad", "terrible"];
const words = text.toLowerCase().split(" ");
let score = 0;
words.forEach(word => {
if(positiveWords.includes(word)) score++;
if(negativeWords.includes(word)) score--;
});
return score > 0 ? "Positive" : score < 0 ? "Negative" : "Neutral";
}
console.log(sentiment("I feel good today")); // > Positive

Connecting to AI APIs

OpenAI API Setup

<!-- Basic setup for OpenAI API using fetch -->
const apiKey = "YOUR_OPENAI_API_KEY";
async function callOpenAI(prompt) {
const response = await fetch("https://api.openai.com/v1/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`
},
body: JSON.stringify({
model: "text-davinci-003",
prompt: prompt,
max_tokens: 50
})
});
const data = await response.json();
return data.choices[0].text;
}
callOpenAI("Hello AI!").then(console.log);

Gemini / Bard API

<!-- Example pseudocode for calling Gemini or Bard API -->
async function callGeminiAPI(query) {
const response = await fetch("https://api.gemini.example.com/v1/query", {
method: "POST",
headers: { "Authorization": "Bearer YOUR_GEMINI_API_KEY" },
body: JSON.stringify({ query: query })
});
const result = await response.json();
return result.answer;
}
callGeminiAPI("Tell me about AI").then(console.log);

Hugging Face Transformers

<!-- Call Hugging Face inference API for a transformer model -->
async function callHuggingFace(input) {
const response = await fetch("https://api-inference.huggingface.co/models/distilbert-base-uncased", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_HUGGINGFACE_API_TOKEN",
"Content-Type": "application/json"
},
body: JSON.stringify({ inputs: input })
});
const data = await response.json();
return data[0].label;
}
callHuggingFace("I love AI").then(console.log);

LangChain in Next.js

<!-- Example: Simple LangChain prompt call in Next.js API route -->
import { NextApiRequest, NextApiResponse } from 'next';
import { LLMChain } from 'langchain/chains';
import { OpenAI } from 'langchain/llms/openai';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const llm = new OpenAI();
const chain = new LLMChain({ llm, prompt: "Say hello to AI" });
const response = await chain.call({});
res.status(200).json({ result: response.text });
}

Calling AI APIs from API Routes

<!-- Example Next.js API route calling OpenAI -->
export default async function handler(req, res) {
const response = await fetch("https://api.openai.com/v1/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
},
body: JSON.stringify({
model: "text-davinci-003",
prompt: req.body.prompt,
max_tokens: 100
})
});
const data = await response.json();
res.status(200).json(data);
}

Streaming AI Responses

<!-- Example to handle streaming response from OpenAI -->
async function streamOpenAIResponse() {
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`
},
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: "Stream this response" }],
stream: true
})
});
const reader = response.body.getReader();
while(true) {
const { done, value } = await reader.read();
if(done) break;
const chunk = new TextDecoder().decode(value);
console.log(chunk);
}
}

Error Handling with AI

<!-- Basic try-catch for API call -->
async function safeCallAI() {
try {
const res = await fetch("https://api.openai.com/v1/completions", { method: "POST", headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}` },
body: JSON.stringify({ model: "text-davinci-003", prompt: "Hello", max_tokens: 5 })
});
const data = await res.json();
return data.choices[0].text;
} catch (error) {
console.error("Error calling AI API:", error);
return "Sorry, something went wrong.";
}
}

Logging AI Usage

<!-- Simple console logging for AI usage tracking -->
function logAIUsage(userId, prompt) {
console.log(`User ${userId} sent prompt: ${prompt}`);
}
logAIUsage("user123", "Explain AI");

Rate Limiting AI Calls

<!-- Basic rate limiting with a counter -->
const userCalls = {};
function canCallAPI(userId) {
if(!userCalls[userId]) userCalls[userId] = 0;
if(userCalls[userId] >= 5) return false;
userCalls[userId]++;
return true;
}
console.log(canCallAPI("user123")); // > true
console.log(canCallAPI("user123")); // > true

Securing API Keys

<!-- Use environment variables and never expose keys in frontend code -->
/* .env.local file */
NEXT_PUBLIC_API_KEY= # <-- Don't expose keys here
OPENAI_API_KEY=your-secret-key

/* Access in Next.js backend code */
const apiKey = process.env.OPENAI_API_KEY;
Middleware for AI Routing
<!-- Simple Express middleware to route AI requests -->
function aiRoutingMiddleware(req, res, next) {
if(req.path.startsWith('/api/ai')) {
console.log('AI request routed:', req.path);
// Custom AI logic or validation here
}
next();
}
app.use(aiRoutingMiddleware);
Caching AI Responses
<!-- Simple in-memory cache example for AI responses -->
const aiCache = {};
async function getAIResponse(prompt) {
if(aiCache[prompt]) {
return aiCache[prompt]; // Return cached response
}
const response = await callAI(prompt);
aiCache[prompt] = response;
return response;
}
AI Rate Policies
<!-- Rate limit middleware for AI calls -->
const rateLimits = {};
function rateLimitMiddleware(req, res, next) {
const user = req.user.id;
rateLimits[user] = (rateLimits[user] || 0) + 1;
if(rateLimits[user] > 100) {
return res.status(429).send('Rate limit exceeded');
}
next();
}
app.use('/api/ai', rateLimitMiddleware);
Edge AI Middleware
<!-- Example of edge middleware in Next.js to add AI headers -->
export function middleware(req) {
const url = req.nextUrl.clone();
if(url.pathname.startsWith('/api/ai')) {
return new Response('Edge AI Middleware Applied', {
headers: { 'X-AI-Edge': 'true' }
});
}
return NextResponse.next();
}
User Role AI Access
<!-- Middleware checking user roles before AI access -->
function roleCheckMiddleware(req, res, next) {
const userRole = req.user.role;
if(userRole !== 'admin' && userRole !== 'ai_user') {
return res.status(403).send('Access denied to AI');
}
next();
}
app.use('/api/ai', roleCheckMiddleware);
Tokenization and Privacy
<!-- Simple token masking function for privacy -->
function maskToken(token) {
return token.slice(0,4) + '****' + token.slice(-4);
}
const token = "123456789abcdef";
console.log(maskToken(token)); // Outputs: 1234****cdef
Using Redis for AI Cache
<!-- Example using Redis for caching AI responses -->
import redis from 'redis';
const client = redis.createClient();

async function getCachedAIResponse(prompt) {
return new Promise((resolve, reject) => {
client.get(prompt, async (err, reply) => {
if(reply) {
resolve(reply);
} else {
const response = await callAI(prompt);
client.set(prompt, response);
resolve(response);
}
});
});
}
AI Activity Monitoring
<!-- Simple logging for AI usage monitoring -->
function monitorAIActivity(userId, action) {
console.log(`[AI Monitor] User: ${userId}, Action: ${action}, Time: ${new Date().toISOString()}`);
}
monitorAIActivity('user123', 'called AI model');
AI Logging with Sentry
<!-- Basic Sentry logging setup for AI errors -->
import * as Sentry from '@sentry/node';
Sentry.init({ dsn: 'YOUR_SENTRY_DSN' });

try {
// call AI API
} catch(error) {
Sentry.captureException(error);
}
Personalization Middleware
<!-- Middleware to attach user preferences for AI personalization -->
function personalizationMiddleware(req, res, next) {
req.userPreferences = { theme: 'dark', language: 'en' };
next();
}
app.use(personalizationMiddleware);
AI Blog Generator
<!-- Example Next.js API route calling AI to generate blog content -->
export default async function handler(req, res) {
const prompt = req.body.prompt;
const blogContent = await callAItoGenerateBlog(prompt);
res.status(200).json({ content: blogContent });
}

async function callAItoGenerateBlog(prompt) {
// Simulated AI response for demo
return `Blog post about: ${prompt}`;
}
AI Product Description Generator
<!-- Simple function to generate product description using AI -->
async function generateProductDescription(productName) {
return `This is a great product called ${productName} that solves many problems.`;
}

generateProductDescription('Smart Watch').then(console.log);
Resume Builder with AI
<!-- AI-powered resume builder function -->
async function buildResume(data) {
return `Resume for ${data.name}: Experienced in ${data.skills.join(', ')}.`;
}

buildResume({ name: 'Alice', skills: ['JavaScript', 'React'] }).then(console.log);
AI Tutor App
<!-- Simple AI tutor answer generator -->
async function tutorAnswer(question) {
return `Answer to your question "${question}" is: ... (AI generated).`;
}

tutorAnswer('What is Next.js?').then(console.log);
Recipe Generator App
<!-- Generate recipe based on ingredients -->
async function generateRecipe(ingredients) {
return `Recipe using ${ingredients.join(', ')}: ... (AI generated).`;
}

generateRecipe(['tomato', 'basil']).then(console.log);
AI Comic Dialogue Generator
<!-- Generate comic dialogue for characters -->
async function generateDialogue(characters) {
return `${characters[0]} says hello to ${characters[1]}.`;
}

generateDialogue(['Hero', 'Sidekick']).then(console.log);
Image Description Generator
<!-- Function to generate image descriptions -->
async function describeImage(imageUrl) {
return `Description for image at ${imageUrl}: A beautiful scene.`;
}

describeImage('http://example.com/image.jpg').then(console.log);
Real-time Voice Translator
<!-- Mock function to translate spoken text in real-time -->
async function translateVoice(text, targetLang) {
return `Translated "${text}" to ${targetLang}.`;
}

translateVoice('Hello', 'es').then(console.log);
AI-powered Search Bar
<!-- Simple AI-enhanced search function -->
async function aiSearch(query) {
return `Search results for "${query}" (AI enhanced).`;
}

aiSearch('Next.js tutorials').then(console.log);
AI + Next.js + Vercel Deployment
<!-- Basic deployment config example -->
module.exports = {
target: 'serverless',
// Vercel handles Next.js API routes
};

<!-- Deployment is done via Vercel CLI or Git integration -->