1. next’s 설치
npx create-next-app@latest 폴더명
설정 질의는 버전별로 좀 다른듯
cd 폴더명
❯ npx create-next-app@latest --ts
✔ What is your project named? … nextjs-init
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ Would you like to use experimental `app/` directory with this project? … No / Yes
✔ What import alias would you like configured? … @/*
2 . TypeScript 설정
이건 설치할떄기본으로 해줌
tsconfig.json 생성 , typescript @types/react @types/node 설치하기
touch tsconfig.json
npm install --save-dev typescript @types/react @types/node
3. Tailwind CSS 또는 Styled Components 설정
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npm install -g sass // sass 설치
npm install -D @tailwindcss/forms // form 설치
npm install --save-dev sass
3.1 global.css 수정
globals.css는 _app.tsx에서 import할 경우 모든 page를 대상으로 적용하는 css이다.
Tailwind를 사용하는 경우에는 이 내용을 전부 지우고 다음과 같이 수정한다.
@tailwind base; @tailwind components; @tailwind utilities;
3.2 form 설치 후 추가
스타일 공통정의에 유용함
: A plugin that provides a basic reset for form styles that makes form elements easy to override with utilities.
: 유틸리티를 사용하여 양식 요소를 쉽게 재정의할 수 있도록 양식 스타일을 기본적으로 재설정하는 플러그인입니다.
https://github.com/tailwindlabs/tailwindcss-forms
GitHub - tailwindlabs/tailwindcss-forms: A plugin that provides a basic reset for form styles that makes form elements easy to o
A plugin that provides a basic reset for form styles that makes form elements easy to override with utilities. - tailwindlabs/tailwindcss-forms
github.com
https://tailwindcss-forms.vercel.app/
@tailwindcss/forms examples
tailwindcss-forms.vercel.app
// tailwind.config.js
module.exports = {
theme: {
// ...
},
plugins: [
require('@tailwindcss/forms'),
// ...
],
}
tailwind.config.ts
ts 파일이 js 를 포함할때 에러날수있음 js 파일 삭제필요
또는 js 만 쓰는경우도 있음
typescript 를 쓰므로 js 삭제함
4.NextAuth.js 설정 (로그인 구현)
npm i bcryptjs next-auth
npm i --save-dev @types/bcryptjs
openssl rand -base64 32
4.1 NextAuth 설정 파일 생성
// src/pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
export default NextAuth({
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
const res = await fetch(`${process.env.NEXTAUTH_URL}/api/auth/login`, {
method: 'POST',
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password,
}),
headers: { 'Content-Type': 'application/json' },
});
const user = await res.json();
// 로그인 성공 시 사용자 정보 반환
if (res.ok && user) {
return user;
}
// 로그인 실패 시 null 반환
return null;
},
}),
],
pages: {
signIn: '/auth/signin', // 로그인 페이지 경로
},
session: {
strategy: 'jwt', // JWT를 사용한 세션 관리
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.email = user.email;
}
return token;
},
async session({ session, token }) {
session.user.id = token.id;
session.user.email = token.email;
return session;
},
}
});
4.2 로그인 페이지 생성
pages/auth/signin.tsx 파일을 생성하여 로그인 페이지를 만듭니다.
// src/app/auth/signin/page.tsx
'use client';
import { signIn } from 'next-auth/react';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function SignIn() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const res = await signIn('credentials', {
redirect: false,
email,
password,
});
if (res?.error) {
setError(res.error);
} else {
router.push('/dashboard'); // 로그인 후 대시보드로 이동
}
};
return (
<div>
<h1>Sign In</h1>
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
{error && <p>{error}</p>}
<button type="submit">Sign In</button>
</form>
</div>
);
}
4.3 SessionProvider로 세션 전역 관리
// src/app/layout.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
import './globals.css';
import { ReactNode } from 'react';
export default function RootLayout({
children,
session, // session prop을 받습니다.
}: {
children: ReactNode;
session: any;
}) {
return (
<html lang="en">
<body>
<SessionProvider session={session}>
{children}
</SessionProvider>
</body>
</html>
);
}
4.4 사용자인증 로직 추가 필요
- 서버 or db 연결 및 로직추가
npm install uuid
5. Vercel 배포 및 PostgreSQL을 Vercel에서 호스팅하고 사용
5.1 @vercel/postgres 설치
npm install @vercel/postgres
5.2. PostgreSQL 연결 및 데이터베이스 설정
https://nextjs.org/learn/dashboard-app/setting-up-your-database#create-a-postgres-database
App Router: Setting Up Your Database | Next.js
Setup a database for your application and seed it with initial data.
nextjs.org
.env 파일에 데이터베이스 연결 정보 추가
DATABASE_URL=postgres://<username>:<password>@<hostname>:<port>/<database_name>
.env.local 파일 설정
NEXTAUTH_URL=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:3000/api
- .env.local (로컬 개발 환경)
- .env.development (개발 환경)
- .env.production (프로덕션 환경)
- .env (기본 환경)
6. Middleware 와 approute 로 인증세션 및 페이지 이동 흐름
- App Router와 Middleware는 서로 상호보완적인 방식으로 사용가능
- App Router는 페이지와 컴포넌트의 구조와 데이터 페칭을 처리하는 방식
- Middleware는 경로 기반의 전역적인 요청 처리나 인증을 미리 확인하는 용도로 사용
src/middleware.ts //프로젝트 루트에 middleware.ts 파일을 생성
6.1. NextAuth.js의 getToken 사용
6.2. Middleware 코드 예시
// src/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getToken } from 'next-auth/jwt';
// 보호된 경로에 대해 인증을 처리
export async function middleware(req: NextRequest) {
// 토큰 가져오기 (세션 확인)
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
// 요청 URL
const { pathname } = req.nextUrl;
// 인증되지 않은 사용자가 보호된 페이지에 접근하려고 할 때 로그인 페이지로 리디렉션
if (!token && pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/auth/signin', req.url));
}
// 세션이 있는 경우 요청을 계속 진행
return NextResponse.next();
}
// 보호할 경로 설정
export const config = {
matcher: ['/dashboard/:path*', '/profile/:path*'], // 보호할 경로 설정 (예: 대시보드, 프로필)
};
6.3. 환경 변수 설정
//.env.local 파일에 NEXTAUTH_SECRET 추가
NEXTAUTH_SECRET=your-secret-key
7. NextAuth.js 설정을 하나의 모듈로 분리해서 재사용 가능하게 하기
7.1. authOptions 분리하기
// src/lib/auth.ts
import CredentialsProvider from 'next-auth/providers/credentials';
import NextAuth from 'next-auth';
export const authOptions = {
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const res = await fetch(`${process.env.NEXTAUTH_URL}/api/auth/login`, {
method: 'POST',
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password,
}),
headers: { 'Content-Type': 'application/json' },
});
const user = await res.json();
if (res.ok && user) {
return user;
}
return null;
},
}),
],
session: {
strategy: 'jwt',
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.email = user.email;
}
return token;
},
async session({ session, token }) {
session.user.id = token.id;
session.user.email = token.email;
return session;
},
},
};
export default NextAuth(authOptions);
7.2 NextAuth API 라우트에서 authOptions 가져오기
// src/pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth';
import { authOptions } from '@/lib/auth'; // authOptions를 가져옴
export default NextAuth(authOptions);
7.3. 서버 컴포넌트에서 authOptions 사용하기
// src/app/dashboard/page.tsx
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth'; // authOptions를 가져옴
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await getServerSession(authOptions);
if (!session) {
redirect('/auth/signin'); // 세션이 없으면 로그인 페이지로 리디렉션
}
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {session.user?.email}</p>
</div>
);
}
7.4. 클라이언트 컴포넌트와 서버 컴포넌트 분리
// src/app/dashboard/DashboardClient.tsx (클라이언트 컴포넌트)
'use client';
import { signOut } from 'next-auth/react';
export default function DashboardClient({ session }) {
return (
<div>
<p>Welcome, {session.user?.email}</p>
<button onClick={() => signOut({ callbackUrl: '/auth/signin' })}>
Sign Out
</button>
</div>
);
}
7.5 클라이언트 컴포넌트에서 이벤트 처리
// src/app/dashboard/page.tsx (서버 컴포넌트)
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth'; // NextAuth 설정
import { redirect } from 'next/navigation';
import DashboardClient from './DashboardClient'; // 클라이언트 컴포넌트 가져옴
export default async function DashboardPage() {
const session = await getServerSession(authOptions);
if (!session) {
redirect('/auth/signin'); // 세션이 없으면 로그인 페이지로 리디렉션
}
// 서버 컴포넌트에서 세션을 전달하고, 클라이언트 컴포넌트로 렌더링
return (
<div>
<h1>Dashboard</h1>
<DashboardClient session={session} />
</div>
);
}
8. Middleware에서 세션을 확인하고, 세션이 없으면 로그인 페이지로 리디렉션하면서 **callbackUrl**을 설정하여 로그인 후 원래 페이지
8.1. Middleware에서 동적으로 callbackUrl 설정
// src/middleware.ts
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
export async function middleware(req) {
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
const { pathname } = req.nextUrl;
// 세션이 없고 보호된 경로에 접근하려고 할 때
if (!token && pathname !== '/auth/signin') {
const url = new URL('/auth/signin', req.url); // 로그인 페이지로 리디렉션
url.searchParams.set('callbackUrl', req.url); // 원래 요청한 경로를 callbackUrl로 설정
return NextResponse.redirect(url); // 로그인 후 돌아올 경로 설정
}
return NextResponse.next(); // 세션이 있으면 요청을 계속 진행
}
export const config = {
matcher: ['/pages/:path*'], // 모든 보호된 경로 설정
};
8.2. 로그인 페이지에서 callbackUrl 처리
// src/pages/auth/signin.tsx
'use client';
import { signIn } from 'next-auth/react';
import { useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
export default function SignIn() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const searchParams = useSearchParams();
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard'; // 로그인 후 리디렉션할 URL
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const res = await signIn('credentials', {
redirect: false,
email,
password,
});
if (res?.error) {
setError(res.error);
} else {
// 로그인 성공 시 callbackUrl로 이동
router.push(callbackUrl);
}
};
return (
<div>
<h1>Sign In</h1>
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
{error && <p>{error}</p>}
<button type="submit">Sign In</button>
</form>
</div>
);
}
8.3. 서버 컴포넌트에서 동적 리디렉션
// src/pages/usesetting.tsx
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth'; // NextAuth 설정
import { redirect } from 'next/navigation';
export default async function UserSettingPage() {
const session = await getServerSession(authOptions);
if (!session) {
// 로그인 페이지로 리디렉션하면서 원래 경로를 callbackUrl로 설정
redirect(`/auth/signin?callbackUrl=/pages/usesetting`);
}
return (
<div>
<h1>User Setting</h1>
<p>Welcome, {session.user?.email}</p>
</div>
);
}
'Dev > Nextjs' 카테고리의 다른 글
[Nextjs] 공통 인터페이스를 이용해서 컴포넌트 생성하기 (0) | 2024.09.25 |
---|---|
[SVGR] SVG 파일 컴포넌트 사용 (0) | 2024.09.22 |
[Nextjs]Vercel + TypeORM (0) | 2024.09.05 |
[Nextjs] Type 추가하는 방법 (0) | 2024.09.05 |
Nextjs + Typescript + Tailwind + storybook - 로컬폰트 설정하기 (0) | 2024.05.18 |