Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/skyybbanerjee/nextjs-getting-started
All about getting started with NextJs ⚛️⚡ + TypeScript 🟦
https://github.com/skyybbanerjee/nextjs-getting-started
nextjs nextjs-app-router nextjs-typescript reactjs tailwindcss typescript
Last synced: 3 days ago
JSON representation
All about getting started with NextJs ⚛️⚡ + TypeScript 🟦
- Host: GitHub
- URL: https://github.com/skyybbanerjee/nextjs-getting-started
- Owner: skyybbanerjee
- Created: 2024-07-22T17:59:01.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2024-07-22T17:59:28.000Z (6 months ago)
- Last Synced: 2024-12-27T19:29:42.689Z (12 days ago)
- Topics: nextjs, nextjs-app-router, nextjs-typescript, reactjs, tailwindcss, typescript
- Language: TypeScript
- Homepage:
- Size: 5.92 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
## Node Version
The minimum Node.js version has been bumped from 16.14 to 18.17, since 16.x has reached end-of-life.
## Create Next App
```sh
npx create-next-app@latest appName
```## Folder Structure
- app folder - where we will spend most of our time
- setup routes, layouts, loading states, etc
- node_modules - project dependencies
- public - static assets
- .gitignore - sets which will be ignored by source control
- bunch of config files (will discuss as we go)
- in package.json look scripts
- 'npm run dev' to spin up the project on http://localhost:3000```sh
npm run dev
```- in globals.css remove everything after directives
- get a hold of the README.md## Home Page
- page.tsx in the root of app folder
- represents root of our application
'/' local domain or production domain
- react component (server component)
- bunch of css classes (will discuss Tailwind in few lectures)
- export component as default
- file name "page" has a special meaning
- snippets extensionapp/page.tsx
```tsx
const HomePage = () => {
return (
HomePage
);
};
export default HomePage;
```## Create Pages in Next.js
- in the app folder create a folder with the page.js file
- about/page.js
- contact/page.js
- can have .js .jsx .tsx extensionapp/about/page.tsx
```tsx
const AboutPage = () => {
return (
AboutPage
);
};
export default AboutPage;
```## Link Component
- navigate around the project
- import Link from 'next/link'
home page```tsx
import Link from 'next/link';
const HomePage = () => {
return (
HomePage
about page
);
};
export default HomePage;
```## Nested Routes
- app/info/contact/page.tsx
- if no page.tsx in a segment will result in 404```tsx
function ContactPage() {
returnContactPage
;
}
export default ContactPage;
```## CSS and Tailwind
- vanilla css in globals.css
- [Tailwind](https://tailwindcss.com/)## Layouts and Templates
- layout.tsx
- template.tsxLayout is a component which wraps other pages and layouts. Allow to share UI. Even when the route changes, layout DOES NOT re-render. Can fetch data but can't pass it down to children. Templates are the same but they re-render.
- the top-most layout is called the Root Layout. This required layout is shared across all pages in an application. Root layouts must contain html and body tags.
- any route segment can optionally define its own Layout. These layouts will be shared across all pages in that segment.
- layouts in a route are nested by default. Each parent layout wraps child layouts below it using the React children prop.```tsx
import './globals.css';export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
hello there
{children}
);
}
```## Challenge - Navbar
- create components/Navbar.tsx
- render in layout.tsx```tsx
import Link from 'next/link';function Navbar() {
return (
Home
Counter
Tours
Actions
);
}
export default Navbar;
``````tsx
import Navbar from '@/components/Navbar';return (
{children}
);
```## Fonts - Google Fonts
Automatically self-host any Google Font. Fonts are included in the deployment and served from the same domain as your deployment. No requests are sent to Google by the browser.
```tsx
import './globals.css';
import { Inter } from 'next/font/google';const inter = Inter({ subsets: ['latin'] });
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
hello there
{children}
);
}
```## Metadata
Next.js has a Metadata API that can be used to define your application metadata (e.g. meta and link tags inside your HTML head element) for improved SEO and web shareability.To define static metadata, export a Metadata object from a layout.tsx or page.tsx file.
```tsx
import type { Metadata } from 'next';export const metadata: Metadata = {
title: 'Next.js Project',
description: 'A Next.js project with TypeScript and TailwindCSS.',
keywords: 'Next.js, Typescript, TailwindCSS',
};
```## Server Components VS Client Components
- [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components)
- [Client Components](https://nextjs.org/docs/app/building-your-application/rendering/client-components)- BY DEFAULT, NEXT.JS USES SERVER COMPONENTS !!!!
- To use Client Components, you can add the React "use client" directive### Server Components
Benefits :
- data fetching
- security
- caching
- bundle sizeData Fetching: Server Components allow you to move data fetching to the server, closer to your data source. This can improve performance by reducing time it takes to fetch data needed for rendering, and the amount of requests the client needs to make.
Security: Server Components allow you to keep sensitive data and logic on the server, such as tokens and API keys, without the risk of exposing them to the client.
Caching: By rendering on the server, the result can be cached and reused on subsequent requests and across users. This can improve performance and reduce cost by reducing the amount of rendering and data fetching done on each request.
Bundle Sizes: Server Components allow you to keep large dependencies that previously would impact the client JavaScript bundle size on the server. This is beneficial for users with slower internet or less powerful devices, as the client does not have to download, parse and execute any JavaScript for Server Components.
Initial Page Load and First Contentful Paint (FCP): On the server, we can generate HTML to allow users to view the page immediately, without waiting for the client to download, parse and execute the JavaScript needed to render the page.
Search Engine Optimization and Social Network Shareability: The rendered HTML can be used by search engine bots to index your pages and social network bots to generate social card previews for your pages.
Streaming: Server Components allow you to split the rendering work into chunks and stream them to the client as they become ready. This allows the user to see parts of the page earlier without having to wait for the entire page to be rendered on the server.### Client Components
Benefits :
- Interactivity: Client Components can use state, effects, and event listeners, meaning they can provide immediate feedback to the user and update the UI.
- Browser APIs: Client Components have access to browser APIs, like geolocation or localStorage, allowing you to build UI for specific use cases.### Challenge
- create counter page and setup basic counter
```tsx
'use client';
import { useState } from 'react';function Counter() {
const [count, setCount] = useState(0);
return (
{count}
setCount(count + 1)}
className='bg-blue-500 rounded-md text-white px-4 py-2 mt-4'
>
Increment
);
}
export default Counter;
```## Challenge - Refactor
- create Counter component and import in CounterPage
- now page can be server component```tsx
import Counter from '@/components/Counter';function CounterPage() {
return (
Page Content
);
}
export default CounterPage;
```## Fetch Data in Server Components
- create tours/page.tsx
- just add async and start using await 🚀🚀🚀
- the same for db
- Next.tsx extends the native Web fetch() API to allow each request on the server to set its own persistent caching semantics.```tsx
const url = 'https://www.course-api.com/react-tours-project';type Tour = {
id: string;
name: string;
info: string;
image: string;
price: string;
};async function ToursPage() {
const response = await fetch(url);
const data: Tour[] = await response.json();
console.log(data);
return (
Tours
{data.map((tour) => {
return{tour.name}
;
})}
);
}
export default ToursPage;
```## Refactor and Delay
- Refresh browser on another page, navigate to tours, observe delay.
```tsx
const fetchTours = async () => {
await new Promise((resolve) => setTimeout(resolve, 3000));
const response = await fetch(url);
const data: Tour[] = await response.json();
return data;
};async function ToursPage() {
const data = await fetchTours();
}
```## Loading Component
The special file loading.js helps you create meaningful Loading UI with React Suspense. With this convention, you can show an instant loading state from the server while the content of a route segment loads. The new content is automatically swapped in once rendering is complete.
- tours/loading.tsx
```tsx
'use client';
const loading = () => {
return loading tours...;
};
export default loading;
```## Error Component
The error.tsx file convention allows you to gracefully handle unexpected runtime errors in nested routes.
- tours/error.js
- 'use client'```js
'use client';
const error = () => {
returnthere was an error...;
};
export default error;
```## Nested Layouts
- create app/tours/layout.tsx
- UI will be applied to app/tours - segment
- don't forget about the "children"
- we can fetch data in the layout but...
at the moment can't pass data down to children (pages) 😞layout.tsx
```js
function ToursLayout({ children }: { children: React.ReactNode }) {
return (
Nested Layout
{children}
);
}
export default ToursLayout;
```## Dynamic Routes
- app/tours/[id]/page.tsx
```js
const page = ({ params }: { params: { id: string } }) => {
console.log(params);return (
ID : {params.id}
);
};
export default page;
```## Challenge - Setup Links
```tsx
return (
Tours
{data.map((tour) => {
return (
{tour.name}
);
})}
);
```## Next Image Component
- get random image from pexels site
[Random Image](https://www.pexels.com/photo/assorted-map-pieces-2859169/)The Next.js Image component extends the HTML element with features for automatic image optimization:
- Size Optimization: Automatically serve correctly sized images for each device, using modern image formats like WebP and AVIF.
- Visual Stability: Prevent layout shift automatically when images are loading.
- Faster Page Loads: Images are only loaded when they enter the viewport using native browser lazy loading, with optional blur-up placeholders.
- Asset Flexibility: On-demand image resizing, even for images stored on remote servers- disable cache
- width and height- priority property to prioritize the image for loading
When true, the image will be considered high priority and preload.```tsx
import mapsImg from '@/images/maps.jpg';
import Image from 'next/image';
const url = 'https://www.course-api.com/images/tours/tour-1.jpeg';const page = async ({ params }: { params: { id: string } }) => {
return (
ID : {params.id}
local image
);
};
export default page;
```## Remote Images
- To use a remote image, the src property should be a URL string.
- Since Next.js does not have access to remote files during the build process, you'll need to provide the width, height and optional blurDataURL props manually.
- The width and height attributes are used to infer the correct aspect ratio of image and avoid layout shift from the image loading in. The width and height do not determine the rendered size of the image file.
```tsx
import mapsImg from '@/images/maps.jpg';
import Image from 'next/image';
const url = 'https://www.course-api.com/images/tours/tour-1.jpeg';const page = async ({ params }: { params: { id: string } }) => {
return (
ID : {params.id}
local image
remote image
);
};
export default page;
``````mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'www.course-api.com',
port: '',
pathname: '/images/**',
},
],
},
};export default nextConfig;
```- To safely allow optimizing images, define a list of supported URL patterns in next.config.mjs. Be as specific as possible to prevent malicious usage.
- restart dev server
## Responsive Images
- The fill prop allows your image to be sized by its parent element
- sizes property helps the browser select the most appropriate image size to load based on the user's device and screen size, improving website performance and user experience.A string that provides information about how wide the image will be at different breakpoints. The value of sizes will greatly affect performance for images using fill or which are styled to have a responsive size.
The sizes property serves two important purposes related to image performance:
First, the value of sizes is used by the browser to determine which size of the image to download, from next/image's automatically-generated source set. When the browser chooses, it does not yet know the size of the image on the page, so it selects an image that is the same size or larger than the viewport. The sizes property allows you to tell the browser that the image will actually be smaller than full screen. If you don't specify a sizes value in an image with the fill property, a default value of 100vw (full screen width) is used.
Second, the sizes property configures how next/image automatically generates an image source set. If no sizes value is present, a small source set is generated, suitable for a fixed-size image. If sizes is defined, a large source set is generated, suitable for a responsive image. If the sizes property includes sizes such as 50vw, which represent a percentage of the viewport width, then the source set is trimmed to not include any values which are too small to ever be necessary.
tours.tsx
```js
return (
{data.map((tour) => {
return (
{tour.name}
);
})}
);
```## More Routing
- Private Folders
\_folder
- Route Groups
(dashboard)
- Dynamic Routes- [...folder] - Catch-all route segment
- [[...folder]] Optional catch-all route segment (used by Clerk)- create test folder app/\_css
- create app/(dashboard)/auth- the url is just '/auth'
- create app/(dashboard)/auth/[sign-in]
```ts
const SignInPage = ({ params }: { params: { 'sign-in': string } }) => {
console.log(params);
returnSignInPage;
};
export default SignInPage;
```- create app/(dashboard)/auth/[...sign-in]
- create app/(dashboard)/auth/[[...sign-in]]```ts
const SignInPage = ({ params }: { params: { 'sign-in': string[] } }) => {
console.log(params);
console.log(params['sign-in'][1]);
returnSignInPage :{params['sign-in'][1]};
};
export default SignInPage;
```## Server Actions
- asynchronous server functions that can be called directly from your components.
- typical setup for server state mutations (create, update, delete)
- endpoint on the server (api route on Next.js)
- make request from the front-end
- setup form, handle submission etc- Next.js server actions allow you to mutate server state directly from within a React component by defining server-side logic alongside client-side interactions.
Rules :
- must be async
- add 'use server' in function body (only in RSC)
- can use in RCC but only as importRSC - React Server Component
RCC - React Client Component```tsx
export default function ServerComponent() {
async function myAction(formData) {
'use server';
// access input values with formData
// formData.get('name')
// mutate data (server)
// revalidate cache
}return ...;
}
```- or setup in a separate file ('use server' at the top)
- can use in both (RSC and RCC)utils/actions.js
```tsx
'use server';export async function myAction() {
// ...
}
``````tsx
'use client';import { myAction } from './actions';
export default function ClientComponent() {
return (
Add to Cart
);
}
```## Actions Page - Setup
- create Form and UsersList in components
```tsx
import Form from '@/components/Form';
import UsersList from '@/components/UsersList';function ActionsPage() {
return (
<>
>
);
}
export default ActionsPage;
```## Form - Setup
```tsx
const createUser = async () => {
'use server';
console.log('creating user....');
};function Form() {
return (
create user
submit
);
}
export default Form;const formStyle = 'max-w-lg flex flex-col gap-y-4 shadow rounded p-8';
const inputStyle = 'border shadow rounded py-2 px-3 text-gray-700';
const btnStyle =
'bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded capitalize';
```## Actions File
- create utils/actions.ts
- make "Form" Client Component ('use client')
- import in Form```ts
'use server';export const createUser = async () => {
console.log('creating user....');
};
``````tsx
'use client';import { createUser } from '@/utils/actions';
```## FormData
```ts
export const createUser = async (formData: FormData) => {
const firstName = formData.get('firstName') as string;
const lastName = formData.get('lastName') as string;
const rawData = Object.fromEntries(formData);
console.log(rawData);
console.log({ firstName, lastName });
};
```## Save User
- just as an example
- create "users.json" (root !!!)
- won't work on vercel (deployment)```ts
'use server';import { readFile, writeFile } from 'fs/promises';
type User = {
id: string;
firstName: string;
lastName: string;
};export const createUser = async (formData: FormData) => {
const firstName = formData.get('firstName') as string;
const lastName = formData.get('lastName') as string;
const newUser: User = { firstName, lastName, id: Date.now().toString() };
await saveUser(newUser);
};export const fetchUsers = async (): Promise => {
const result = await readFile('users.json', { encoding: 'utf8' });
const users = result ? JSON.parse(result) : [];
return users;
};const saveUser = async (user: User) => {
const users = await fetchUsers();
users.push(user);
await writeFile('users.json', JSON.stringify(users));
};
```## UsersList
```tsx
import { fetchUsers } from '@/utils/actions';
async function UsersList() {
const users = await fetchUsers();
return (
{users.length ? (
{users.map((user) => (
{user.firstName} {user.lastName}
))}
) : (
No users found...
)}
);
}
export default UsersList;
```## RevalidatePath
```ts
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';export const createUser = async (formData: FormData) => {
//...
revalidatePath('/actions');
};
```- if the data is displayed in a different page
```ts
export const createUser = async (formData: FormData) => {
//...
redirect('/');
};
```- don't "redirect" place inside "try" block
```tsx
try {
await saveUser(newUser);
// will trigger error
redirect('/');
} catch (error) {
console.error(error);
}
```## Pending State
- make sure Form is Client Component
- in createUser switch back to revalidatePath(/actions)[React Docs - useFormStatus](https://react.dev/reference/react-dom/hooks/useFormStatus)
- useFormStatus()
- The useFormStatus Hook provides status information of the last form submission.- The useFormStatus Hook must be called from a component that is rendered inside a .
- useFormStatus will only return status information for a parent .
It will not return status information for any rendered in that same component or children components.```tsx
import { useFormStatus, useFormState } from 'react-dom';const SubmitButton = () => {
const { pending } = useFormStatus();
return (
{pending ? 'submitting...' : 'submit'}
);
};
```## Result
[React Docs - useFormState](https://react.dev/reference/react-dom/hooks/useFormState)
- useFormState()
- a Hook that allows you to update state based on the result of a form action.```tsx
const [message, formAction] = useFormState(createUser, null);
return (
{message &&{message}
}
...
);
``````ts
export const createUser = async (prevState: any, formData: FormData) => {
// current state of the form
console.log(prevState);const firstName = formData.get('firstName') as string;
const lastName = formData.get('lastName') as string;
const newUser: User = { firstName, lastName, id: Date.now().toString() };try {
await saveUser(newUser);
revalidatePath('/actions');
// throw Error();
return 'user created successfully...';
} catch (error) {
console.error(error);
return 'failed to create user...';
}
};
```## Delete User
- create components/DeleteButton
- refactor UsersList```tsx
function DeleteButton({ id }: { id: string }) {
return (
delete
);
}
export default DeleteButton;
``````tsx
import { fetchUsers } from '@/utils/actions';
import DeleteButton from './DeleteButton';
async function UsersList() {
const users = await fetchUsers();
return (
{users.length ? (
{users.map((user) => (
{user.firstName} {user.lastName}
))}
) : (
No users found...
)}
);
}
export default UsersList;
```## Delete Action
```ts
export const deleteUser = async (formData: FormData) => {
const id = formData.get('id') as string;
const users = await fetchUsers();
const updatedUsers = users.filter((user: User) => user.id !== id);
await writeFile('users.json', JSON.stringify(updatedUsers));
revalidatePath('/actions');
};
``````tsx
import { deleteUser } from '@/utils/actions';function DeleteButton({ id }: { id: string }) {
return (
delete
);
}
export default DeleteButton;
``````tsx
import { deleteUser, removeUser } from '@/utils/actions';function DeleteButton({ id }: { id: string }) {
const removeUserWithId = removeUser.bind(null, id);
return (
delete
);
}
export default DeleteButton;
```## Bind Option
- An alternative to passing arguments as hidden input fields in the form (e.g. ``) is to use the bind option. With this approach, the value is not part of the rendered HTML and will not be encoded.
- .bind works in both Server and Client Components. It also supports progressive enhancement.
```ts
export const removeUser = async (id: string, formData: FormData) => {
const name = formData.get('name') as string;
console.log(name);const users = await fetchUsers();
const updatedUsers = users.filter((user) => user.id !== id);
await writeFile('users.json', JSON.stringify(updatedUsers));
revalidatePath('/actions');
};
```## Route Handlers
- install Thunder Client
Route Handlers allow you to create custom request handlers for a given route using the Web Request and Response APIs.
- in app create folder "api"
- in there create folder "users" with route.ts fileThe following HTTP methods are supported: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. If an unsupported method is called, Next.js will return a 405 Method Not Allowed response.
In addition to supporting native Request and Response. Next.js extends them with NextRequest and NextResponse to provide convenient helpers for advanced use cases.
app/api/users/route.ts
```ts
// the following HTTP methods are supported: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. If an unsupported method is called, Next.js will return a 405 Method Not Allowed response.import { NextRequest, NextResponse } from 'next/server';
import { fetchUsers, saveUser } from '@/utils/actions';export const GET = async () => {
const users = await fetchUsers();
return Response.json({ users });
};export const POST = async (request: Request) => {
const user = await request.json();
const newUser = { ...user, id: Date.now().toString() };
await saveUser(newUser);
return Response.json({ msg: 'user created' });
};
``````ts
import { fetchUsers, saveUser } from '@/utils/actions';export const GET = async (request: Request) => {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
console.log(id);const users = await fetchUsers();
return Response.json({ users });
};
``````ts
import { fetchUsers, saveUser } from '@/utils/actions';
import { NextRequest, NextResponse } from 'next/server';export const GET = async (request: NextRequest) => {
console.log(request.url);
console.log(request.nextUrl.searchParams.get('id'));const users = await fetchUsers();
return NextResponse.redirect(new URL('/', request.url));
};
```The URL constructor takes two arguments: url and base. If the url is a relative URL, then base is required. If url is an absolute URL, then base is ignored.
Here, '/' is the url and request.url is the base.
This means it's creating a new URL object that represents the root of the URL contained in request.url.
For example, if request.url is 'http://example.com/path/to/resource', the new URL object would represent 'http://example.com/'.
## Middleware
[Docs](https://nextjs.org/docs/app/building-your-application/routing/middleware)
Middleware in Next.js is a piece of code that allows you to perform actions before a request is completed and modify the response accordingly.
- create middleware.ts in the root
- by default will be invoked for every route in your project```ts
export function middleware(request) {
return Response.json({ msg: 'hello there' });
}export const config = {
matcher: '/about',
};
``````ts
import { NextResponse } from 'next/server';// This function can be marked `async` if using `await` inside
export function middleware(request) {
return NextResponse.redirect(new URL('/', request.url));
}// See "Matching Paths" below to learn more
export const config = {
matcher: ['/about/:path*', '/tours/:path*'],
};
```## Local Build
- cleanup middleware
- fix css in UsersList.tsx
- remove all users from 'users.json'
- 'npm run build' followed by 'npm start'## Caching
- [Vercel Video](https://www.youtube.com/watch?v=VBlSe8tvg4U)
- [Docs](https://nextjs.org/docs/app/building-your-application/caching)