Tools: GitHub Actions workflow to deploy the Next.js Application on Vercel Cloud

Tools: GitHub Actions workflow to deploy the Next.js Application on Vercel Cloud

What We'll Build ## πŸ“‹ Prerequisites ## - Basic knowledge of Git and terminal ## STEP 1: Install Node.js and Required Tools on local machine ## STEP 2: Create Next.js Application ## Create Project Structure ## File 1: package.json ## File 2: next.config.js ## File 3: tsconfig.json ## File 4: .gitignore ## File 5: .env.local ## File 6: app/layout.tsx ## File 7: app/page.tsx ## File 8: app/about/page.tsx ## File 9: app/api/hello/route.ts ## File 10: app/globals.css ## File 11: tailwind.config.ts ## File 12: postcss.config.js ## File 13: jest.config.js ## File 14: jest.setup.js ## File 15: tests/page.test.tsx ## File 16: ecosystem.config.js (PM2 Configuration) ## File 17: README.md ## STEP 3: Install Dependencies ## STEP 4: Build the Application ## STEP 5: Run on Port 80 (Requires sudo) ## Using PM2 ## STEP 6: Test the Application ## STEP 7: Push to GitHub ## STEP 8: Create GitHub Actions Workflow ## STEP 9: Setup Vercel ## 9.1: Install Vercel CLI ## 9.2: Login to Vercel ## 9.3: Link Project to Vercel ## 9.4: Get Vercel Credentials ## STEP 10: Add GitHub Secrets ## STEP 11: Test Full Pipeline ## 🎨 How to Make Changes and See Automatic Deployment ## Understanding the Workflow ## πŸš€ Quick Test: Make Your First Change ## Change Homepage Text (Easiest) ## πŸ“€ Push Changes to GitHub ## πŸ” Watch the Automatic Deployment ## Method 1: Watch on Vercel Dashboard ## 🌐 View Your Changes In this article, I’ll walk you through setting up a GitHub Actions workflow to automatically deploy a Next.js application to Vercel. Continuous deployment helps streamline development and ensures every push is production-ready. We’ll configure secrets, create a workflow file, and connect GitHub with Vercel for seamless deployments. This setup enables automated builds and previews for every commit. Let’s dive into building a simple CI/CD pipeline for your Next.js app. A complete Next.js application that: Now, let's create each file with content: Output will look like: Go to: https://github.com/YOUR_USERNAME/nextjs-app Click: Settings β†’ Secrets and variables β†’ Actions Click: "New repository secret" Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or COMMAND_BLOCK: # Update system sudo apt update && sudo apt upgrade -y # Install Node.js 20.x (LTS) curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs # Verify installation node --version # Should show v20.x.x npm --version # Should show 10.x.x # Install additional tools sudo apt install -y git curl build-essential # Install PM2 (process manager for background running) sudo npm install -g pm2 # Verify PM2 pm2 --version COMMAND_BLOCK: # Update system sudo apt update && sudo apt upgrade -y # Install Node.js 20.x (LTS) curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs # Verify installation node --version # Should show v20.x.x npm --version # Should show 10.x.x # Install additional tools sudo apt install -y git curl build-essential # Install PM2 (process manager for background running) sudo npm install -g pm2 # Verify PM2 pm2 --version COMMAND_BLOCK: # Update system sudo apt update && sudo apt upgrade -y # Install Node.js 20.x (LTS) curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs # Verify installation node --version # Should show v20.x.x npm --version # Should show 10.x.x # Install additional tools sudo apt install -y git curl build-essential # Install PM2 (process manager for background running) sudo npm install -g pm2 # Verify PM2 pm2 --version COMMAND_BLOCK: # Create project directory mkdir -p ~/nextjs-app cd ~/nextjs-app # Create Next.js app structure # We'll create all files manually for complete control COMMAND_BLOCK: # Create project directory mkdir -p ~/nextjs-app cd ~/nextjs-app # Create Next.js app structure # We'll create all files manually for complete control COMMAND_BLOCK: # Create project directory mkdir -p ~/nextjs-app cd ~/nextjs-app # Create Next.js app structure # We'll create all files manually for complete control COMMAND_BLOCK: # Create directories mkdir -p app/{api/hello,components,lib,styles} mkdir -p public/images mkdir -p .github/workflows # Create files touch next.config.js touch package.json touch tsconfig.json touch .gitignore touch .env.local touch README.md touch ecosystem.config.js COMMAND_BLOCK: # Create directories mkdir -p app/{api/hello,components,lib,styles} mkdir -p public/images mkdir -p .github/workflows # Create files touch next.config.js touch package.json touch tsconfig.json touch .gitignore touch .env.local touch README.md touch ecosystem.config.js COMMAND_BLOCK: # Create directories mkdir -p app/{api/hello,components,lib,styles} mkdir -p public/images mkdir -p .github/workflows # Create files touch next.config.js touch package.json touch tsconfig.json touch .gitignore touch .env.local touch README.md touch ecosystem.config.js COMMAND_BLOCK: cat > package.json << 'EOF' { "name": "nextjs-app", "version": "1.0.0", "description": "Complete Next.js application with CI/CD", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start -p 80", "lint": "next lint", "test": "jest", "test:watch": "jest --watch" }, "dependencies": { "next": "14.1.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@types/node": "^20.10.6", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", "autoprefixer": "^10.4.16", "eslint": "^8.56.0", "eslint-config-next": "14.1.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", "typescript": "^5.3.3" } } EOF COMMAND_BLOCK: cat > package.json << 'EOF' { "name": "nextjs-app", "version": "1.0.0", "description": "Complete Next.js application with CI/CD", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start -p 80", "lint": "next lint", "test": "jest", "test:watch": "jest --watch" }, "dependencies": { "next": "14.1.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@types/node": "^20.10.6", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", "autoprefixer": "^10.4.16", "eslint": "^8.56.0", "eslint-config-next": "14.1.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", "typescript": "^5.3.3" } } EOF COMMAND_BLOCK: cat > package.json << 'EOF' { "name": "nextjs-app", "version": "1.0.0", "description": "Complete Next.js application with CI/CD", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start -p 80", "lint": "next lint", "test": "jest", "test:watch": "jest --watch" }, "dependencies": { "next": "14.1.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@types/node": "^20.10.6", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", "autoprefixer": "^10.4.16", "eslint": "^8.56.0", "eslint-config-next": "14.1.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "postcss": "^8.4.32", "tailwindcss": "^3.4.0", "typescript": "^5.3.3" } } EOF COMMAND_BLOCK: cat > next.config.js << 'EOF' /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, swcMinify: true, output: 'standalone', poweredByHeader: false, // Environment variables env: { NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME || 'Next.js App', NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:80', }, // Image optimization images: { domains: ['localhost'], formats: ['image/avif', 'image/webp'], }, // Headers async headers() { return [ { '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'DENY', }, { key: 'X-Content-Type-Options', value: 'nosniff', }, { key: 'Referrer-Policy', value: 'origin-when-cross-origin', }, ], }, ]; }, }; module.exports = nextConfig; EOF COMMAND_BLOCK: cat > next.config.js << 'EOF' /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, swcMinify: true, output: 'standalone', poweredByHeader: false, // Environment variables env: { NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME || 'Next.js App', NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:80', }, // Image optimization images: { domains: ['localhost'], formats: ['image/avif', 'image/webp'], }, // Headers async headers() { return [ { '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'DENY', }, { key: 'X-Content-Type-Options', value: 'nosniff', }, { key: 'Referrer-Policy', value: 'origin-when-cross-origin', }, ], }, ]; }, }; module.exports = nextConfig; EOF COMMAND_BLOCK: cat > next.config.js << 'EOF' /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, swcMinify: true, output: 'standalone', poweredByHeader: false, // Environment variables env: { NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME || 'Next.js App', NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:80', }, // Image optimization images: { domains: ['localhost'], formats: ['image/avif', 'image/webp'], }, // Headers async headers() { return [ { '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'DENY', }, { key: 'X-Content-Type-Options', value: 'nosniff', }, { key: 'Referrer-Policy', value: 'origin-when-cross-origin', }, ], }, ]; }, }; module.exports = nextConfig; EOF COMMAND_BLOCK: cat > tsconfig.json << 'EOF' { "compilerOptions": { "target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } ], "paths": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } EOF COMMAND_BLOCK: cat > tsconfig.json << 'EOF' { "compilerOptions": { "target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } ], "paths": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } EOF COMMAND_BLOCK: cat > tsconfig.json << 'EOF' { "compilerOptions": { "target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } ], "paths": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } EOF COMMAND_BLOCK: cat > .gitignore << 'EOF' # Dependencies node_modules/ /.pnp .pnp.js # Testing /coverage # Next.js /.next/ /out/ .next # Production /build dist # Misc .DS_Store *.pem .vercel # Debug npm-debug.log* yarn-debug.log* yarn-error.log* # Local env files .env*.local .env.production # PM2 .pm2 # IDE .idea/ .vscode/ *.swp *.swo *~ # Logs logs *.log EOF COMMAND_BLOCK: cat > .gitignore << 'EOF' # Dependencies node_modules/ /.pnp .pnp.js # Testing /coverage # Next.js /.next/ /out/ .next # Production /build dist # Misc .DS_Store *.pem .vercel # Debug npm-debug.log* yarn-debug.log* yarn-error.log* # Local env files .env*.local .env.production # PM2 .pm2 # IDE .idea/ .vscode/ *.swp *.swo *~ # Logs logs *.log EOF COMMAND_BLOCK: cat > .gitignore << 'EOF' # Dependencies node_modules/ /.pnp .pnp.js # Testing /coverage # Next.js /.next/ /out/ .next # Production /build dist # Misc .DS_Store *.pem .vercel # Debug npm-debug.log* yarn-debug.log* yarn-error.log* # Local env files .env*.local .env.production # PM2 .pm2 # IDE .idea/ .vscode/ *.swp *.swo *~ # Logs logs *.log EOF COMMAND_BLOCK: cat > .env.local << 'EOF' # Application NEXT_PUBLIC_APP_NAME=My Next.js App NEXT_PUBLIC_API_URL=http://localhost:80 # Environment NODE_ENV=development EOF COMMAND_BLOCK: cat > .env.local << 'EOF' # Application NEXT_PUBLIC_APP_NAME=My Next.js App NEXT_PUBLIC_API_URL=http://localhost:80 # Environment NODE_ENV=development EOF COMMAND_BLOCK: cat > .env.local << 'EOF' # Application NEXT_PUBLIC_APP_NAME=My Next.js App NEXT_PUBLIC_API_URL=http://localhost:80 # Environment NODE_ENV=development EOF COMMAND_BLOCK: cat > app/layout.tsx << 'EOF' import type { Metadata } from 'next' import './globals.css' export const metadata: Metadata = { title: 'Next.js App', description: 'Complete Next.js application with CI/CD', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> ) } EOF COMMAND_BLOCK: cat > app/layout.tsx << 'EOF' import type { Metadata } from 'next' import './globals.css' export const metadata: Metadata = { title: 'Next.js App', description: 'Complete Next.js application with CI/CD', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> ) } EOF COMMAND_BLOCK: cat > app/layout.tsx << 'EOF' import type { Metadata } from 'next' import './globals.css' export const metadata: Metadata = { title: 'Next.js App', description: 'Complete Next.js application with CI/CD', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> ) } EOF COMMAND_BLOCK: cat > app/page.tsx << 'EOF' import Link from 'next/link' export default function Home() { return ( <main className="min-h-screen flex flex-col items-center justify-center p-24 bg-gradient-to-br from-blue-50 to-indigo-100"> <div className="max-w-4xl w-full space-y-8 text-center"> <div className="space-y-4"> <h1 className="text-6xl font-bold text-gray-900"> Welcome to <span className="text-blue-600">Next.js</span> </h1> <p className="text-xl text-gray-600"> A complete Next.js application with CI/CD pipeline </p> </div> <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-12"> <div className="p-6 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow"> <h3 className="text-xl font-semibold mb-2">πŸ“¦ Features</h3> <p className="text-gray-600"> TypeScript, Tailwind CSS, API Routes </p> </div> <div className="p-6 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow"> <h3 className="text-xl font-semibold mb-2">πŸš€ Deploy</h3> <p className="text-gray-600"> Automated deployment to Vercel </p> </div> <div className="p-6 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow"> <h3 className="text-xl font-semibold mb-2">πŸ”§ CI/CD</h3> <p className="text-gray-600"> GitHub Actions workflow included </p> </div> </div> <div className="mt-12 space-x-4"> <Link href="/about" className="inline-block px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors" > About Page </Link> <Link href="/api/hello" className="inline-block px-6 py-3 bg-gray-800 text-white rounded-lg hover:bg-gray-900 transition-colors" > Test API </Link> </div> <div className="mt-8 text-sm text-gray-500"> <p>Running on port 80 β€’ Managed by PM2</p> </div> </div> </main> ) } EOF COMMAND_BLOCK: cat > app/page.tsx << 'EOF' import Link from 'next/link' export default function Home() { return ( <main className="min-h-screen flex flex-col items-center justify-center p-24 bg-gradient-to-br from-blue-50 to-indigo-100"> <div className="max-w-4xl w-full space-y-8 text-center"> <div className="space-y-4"> <h1 className="text-6xl font-bold text-gray-900"> Welcome to <span className="text-blue-600">Next.js</span> </h1> <p className="text-xl text-gray-600"> A complete Next.js application with CI/CD pipeline </p> </div> <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-12"> <div className="p-6 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow"> <h3 className="text-xl font-semibold mb-2">πŸ“¦ Features</h3> <p className="text-gray-600"> TypeScript, Tailwind CSS, API Routes </p> </div> <div className="p-6 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow"> <h3 className="text-xl font-semibold mb-2">πŸš€ Deploy</h3> <p className="text-gray-600"> Automated deployment to Vercel </p> </div> <div className="p-6 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow"> <h3 className="text-xl font-semibold mb-2">πŸ”§ CI/CD</h3> <p className="text-gray-600"> GitHub Actions workflow included </p> </div> </div> <div className="mt-12 space-x-4"> <Link href="/about" className="inline-block px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors" > About Page </Link> <Link href="/api/hello" className="inline-block px-6 py-3 bg-gray-800 text-white rounded-lg hover:bg-gray-900 transition-colors" > Test API </Link> </div> <div className="mt-8 text-sm text-gray-500"> <p>Running on port 80 β€’ Managed by PM2</p> </div> </div> </main> ) } EOF COMMAND_BLOCK: cat > app/page.tsx << 'EOF' import Link from 'next/link' export default function Home() { return ( <main className="min-h-screen flex flex-col items-center justify-center p-24 bg-gradient-to-br from-blue-50 to-indigo-100"> <div className="max-w-4xl w-full space-y-8 text-center"> <div className="space-y-4"> <h1 className="text-6xl font-bold text-gray-900"> Welcome to <span className="text-blue-600">Next.js</span> </h1> <p className="text-xl text-gray-600"> A complete Next.js application with CI/CD pipeline </p> </div> <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-12"> <div className="p-6 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow"> <h3 className="text-xl font-semibold mb-2">πŸ“¦ Features</h3> <p className="text-gray-600"> TypeScript, Tailwind CSS, API Routes </p> </div> <div className="p-6 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow"> <h3 className="text-xl font-semibold mb-2">πŸš€ Deploy</h3> <p className="text-gray-600"> Automated deployment to Vercel </p> </div> <div className="p-6 border border-gray-200 rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow"> <h3 className="text-xl font-semibold mb-2">πŸ”§ CI/CD</h3> <p className="text-gray-600"> GitHub Actions workflow included </p> </div> </div> <div className="mt-12 space-x-4"> <Link href="/about" className="inline-block px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors" > About Page </Link> <Link href="/api/hello" className="inline-block px-6 py-3 bg-gray-800 text-white rounded-lg hover:bg-gray-900 transition-colors" > Test API </Link> </div> <div className="mt-8 text-sm text-gray-500"> <p>Running on port 80 β€’ Managed by PM2</p> </div> </div> </main> ) } EOF COMMAND_BLOCK: mkdir -p app/about cat > app/about/page.tsx << 'EOF' import Link from 'next/link' export default function About() { return ( <main className="min-h-screen flex flex-col items-center justify-center p-24 bg-gradient-to-br from-purple-50 to-pink-100"> <div className="max-w-2xl w-full space-y-8"> <h1 className="text-5xl font-bold text-gray-900 text-center"> About This App </h1> <div className="bg-white p-8 rounded-lg shadow-md space-y-4"> <h2 className="text-2xl font-semibold text-gray-800">Features</h2> <ul className="list-disc list-inside space-y-2 text-gray-600"> <li>Next.js 14 with App Router</li> <li>TypeScript for type safety</li> <li>Tailwind CSS for styling</li> <li>API Routes for backend logic</li> <li>PM2 for process management</li> <li>GitHub Actions for CI/CD</li> <li>Vercel deployment ready</li> </ul> </div> <div className="text-center"> <Link href="/" className="inline-block px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors" > ← Back to Home </Link> </div> </div> </main> ) } EOF COMMAND_BLOCK: mkdir -p app/about cat > app/about/page.tsx << 'EOF' import Link from 'next/link' export default function About() { return ( <main className="min-h-screen flex flex-col items-center justify-center p-24 bg-gradient-to-br from-purple-50 to-pink-100"> <div className="max-w-2xl w-full space-y-8"> <h1 className="text-5xl font-bold text-gray-900 text-center"> About This App </h1> <div className="bg-white p-8 rounded-lg shadow-md space-y-4"> <h2 className="text-2xl font-semibold text-gray-800">Features</h2> <ul className="list-disc list-inside space-y-2 text-gray-600"> <li>Next.js 14 with App Router</li> <li>TypeScript for type safety</li> <li>Tailwind CSS for styling</li> <li>API Routes for backend logic</li> <li>PM2 for process management</li> <li>GitHub Actions for CI/CD</li> <li>Vercel deployment ready</li> </ul> </div> <div className="text-center"> <Link href="/" className="inline-block px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors" > ← Back to Home </Link> </div> </div> </main> ) } EOF COMMAND_BLOCK: mkdir -p app/about cat > app/about/page.tsx << 'EOF' import Link from 'next/link' export default function About() { return ( <main className="min-h-screen flex flex-col items-center justify-center p-24 bg-gradient-to-br from-purple-50 to-pink-100"> <div className="max-w-2xl w-full space-y-8"> <h1 className="text-5xl font-bold text-gray-900 text-center"> About This App </h1> <div className="bg-white p-8 rounded-lg shadow-md space-y-4"> <h2 className="text-2xl font-semibold text-gray-800">Features</h2> <ul className="list-disc list-inside space-y-2 text-gray-600"> <li>Next.js 14 with App Router</li> <li>TypeScript for type safety</li> <li>Tailwind CSS for styling</li> <li>API Routes for backend logic</li> <li>PM2 for process management</li> <li>GitHub Actions for CI/CD</li> <li>Vercel deployment ready</li> </ul> </div> <div className="text-center"> <Link href="/" className="inline-block px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors" > ← Back to Home </Link> </div> </div> </main> ) } EOF COMMAND_BLOCK: cat > app/api/hello/route.ts << 'EOF' import { NextResponse } from 'next/server' export async function GET() { return NextResponse.json({ message: 'Hello from Next.js API!', timestamp: new Date().toISOString(), status: 'success', environment: process.env.NODE_ENV, }) } export async function POST(request: Request) { const body = await request.json() return NextResponse.json({ message: 'Data received successfully', receivedData: body, timestamp: new Date().toISOString(), }) } EOF COMMAND_BLOCK: cat > app/api/hello/route.ts << 'EOF' import { NextResponse } from 'next/server' export async function GET() { return NextResponse.json({ message: 'Hello from Next.js API!', timestamp: new Date().toISOString(), status: 'success', environment: process.env.NODE_ENV, }) } export async function POST(request: Request) { const body = await request.json() return NextResponse.json({ message: 'Data received successfully', receivedData: body, timestamp: new Date().toISOString(), }) } EOF COMMAND_BLOCK: cat > app/api/hello/route.ts << 'EOF' import { NextResponse } from 'next/server' export async function GET() { return NextResponse.json({ message: 'Hello from Next.js API!', timestamp: new Date().toISOString(), status: 'success', environment: process.env.NODE_ENV, }) } export async function POST(request: Request) { const body = await request.json() return NextResponse.json({ message: 'Data received successfully', receivedData: body, timestamp: new Date().toISOString(), }) } EOF COMMAND_BLOCK: cat > app/globals.css << 'EOF' @tailwind base; @tailwind components; @tailwind utilities; :root { --foreground-rgb: 0, 0, 0; --background-start-rgb: 214, 219, 220; --background-end-rgb: 255, 255, 255; } body { color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom, transparent, rgb(var(--background-end-rgb)) ) rgb(var(--background-start-rgb)); } EOF COMMAND_BLOCK: cat > app/globals.css << 'EOF' @tailwind base; @tailwind components; @tailwind utilities; :root { --foreground-rgb: 0, 0, 0; --background-start-rgb: 214, 219, 220; --background-end-rgb: 255, 255, 255; } body { color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom, transparent, rgb(var(--background-end-rgb)) ) rgb(var(--background-start-rgb)); } EOF COMMAND_BLOCK: cat > app/globals.css << 'EOF' @tailwind base; @tailwind components; @tailwind utilities; :root { --foreground-rgb: 0, 0, 0; --background-start-rgb: 214, 219, 220; --background-end-rgb: 255, 255, 255; } body { color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom, transparent, rgb(var(--background-end-rgb)) ) rgb(var(--background-start-rgb)); } EOF COMMAND_BLOCK: cat > tailwind.config.ts << 'EOF' import type { Config } from 'tailwindcss' const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, plugins: [], } export default config EOF COMMAND_BLOCK: cat > tailwind.config.ts << 'EOF' import type { Config } from 'tailwindcss' const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, plugins: [], } export default config EOF COMMAND_BLOCK: cat > tailwind.config.ts << 'EOF' import type { Config } from 'tailwindcss' const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, plugins: [], } export default config EOF COMMAND_BLOCK: cat > postcss.config.js << 'EOF' module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } EOF COMMAND_BLOCK: cat > postcss.config.js << 'EOF' module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } EOF COMMAND_BLOCK: cat > postcss.config.js << 'EOF' module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } EOF COMMAND_BLOCK: cat > jest.config.js << 'EOF' const nextJest = require('next/jest') const createJestConfig = nextJest({ dir: './', }) const customJestConfig = { setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], testEnvironment: 'jest-environment-jsdom', moduleNameMapper: { '^@/(.*)$': '<rootDir>/$1', }, testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], } module.exports = createJestConfig(customJestConfig) EOF COMMAND_BLOCK: cat > jest.config.js << 'EOF' const nextJest = require('next/jest') const createJestConfig = nextJest({ dir: './', }) const customJestConfig = { setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], testEnvironment: 'jest-environment-jsdom', moduleNameMapper: { '^@/(.*)$': '<rootDir>/$1', }, testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], } module.exports = createJestConfig(customJestConfig) EOF COMMAND_BLOCK: cat > jest.config.js << 'EOF' const nextJest = require('next/jest') const createJestConfig = nextJest({ dir: './', }) const customJestConfig = { setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], testEnvironment: 'jest-environment-jsdom', moduleNameMapper: { '^@/(.*)$': '<rootDir>/$1', }, testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)', ], } module.exports = createJestConfig(customJestConfig) EOF COMMAND_BLOCK: cat > jest.setup.js << 'EOF' import '@testing-library/jest-dom' EOF COMMAND_BLOCK: cat > jest.setup.js << 'EOF' import '@testing-library/jest-dom' EOF COMMAND_BLOCK: cat > jest.setup.js << 'EOF' import '@testing-library/jest-dom' EOF COMMAND_BLOCK: mkdir -p __tests__ cat > __tests__/page.test.tsx << 'EOF' import { render, screen } from '@testing-library/react' import Home from '@/app/page' describe('Home Page', () => { it('renders the main heading', () => { render(<Home />) const heading = screen.getByRole('heading', { level: 1 }) expect(heading).toBeInTheDocument() }) it('contains welcome text', () => { render(<Home />) expect(screen.getByText(/Welcome to/i)).toBeInTheDocument() }) }) EOF COMMAND_BLOCK: mkdir -p __tests__ cat > __tests__/page.test.tsx << 'EOF' import { render, screen } from '@testing-library/react' import Home from '@/app/page' describe('Home Page', () => { it('renders the main heading', () => { render(<Home />) const heading = screen.getByRole('heading', { level: 1 }) expect(heading).toBeInTheDocument() }) it('contains welcome text', () => { render(<Home />) expect(screen.getByText(/Welcome to/i)).toBeInTheDocument() }) }) EOF COMMAND_BLOCK: mkdir -p __tests__ cat > __tests__/page.test.tsx << 'EOF' import { render, screen } from '@testing-library/react' import Home from '@/app/page' describe('Home Page', () => { it('renders the main heading', () => { render(<Home />) const heading = screen.getByRole('heading', { level: 1 }) expect(heading).toBeInTheDocument() }) it('contains welcome text', () => { render(<Home />) expect(screen.getByText(/Welcome to/i)).toBeInTheDocument() }) }) EOF COMMAND_BLOCK: cat > ecosystem.config.js << 'EOF' module.exports = { apps: [ { name: 'nextjs-app', script: 'npm', args: 'start', cwd: './', instances: 1, autorestart: true, watch: false, max_memory_restart: '1G', env: { NODE_ENV: 'production', PORT: 80, }, error_file: './logs/err.log', out_file: './logs/out.log', log_file: './logs/combined.log', time: true, }, ], } EOF # Create logs directory mkdir -p logs COMMAND_BLOCK: cat > ecosystem.config.js << 'EOF' module.exports = { apps: [ { name: 'nextjs-app', script: 'npm', args: 'start', cwd: './', instances: 1, autorestart: true, watch: false, max_memory_restart: '1G', env: { NODE_ENV: 'production', PORT: 80, }, error_file: './logs/err.log', out_file: './logs/out.log', log_file: './logs/combined.log', time: true, }, ], } EOF # Create logs directory mkdir -p logs COMMAND_BLOCK: cat > ecosystem.config.js << 'EOF' module.exports = { apps: [ { name: 'nextjs-app', script: 'npm', args: 'start', cwd: './', instances: 1, autorestart: true, watch: false, max_memory_restart: '1G', env: { NODE_ENV: 'production', PORT: 80, }, error_file: './logs/err.log', out_file: './logs/out.log', log_file: './logs/combined.log', time: true, }, ], } EOF # Create logs directory mkdir -p logs COMMAND_BLOCK: cat > README.md << 'EOF' # Next.js Application Complete Next.js application with CI/CD pipeline. ## Features - βœ… Next.js 14 with App Router - βœ… TypeScript - βœ… Tailwind CSS - βœ… API Routes - βœ… Jest Testing - βœ… PM2 Process Management - βœ… GitHub Actions CI/CD - βœ… Vercel Deployment ## Development npm install npm run dev ## Production npm run build npm start ## Testing npm test ## Deployment Automatically deploys to Vercel on push to main branch. EOF COMMAND_BLOCK: cat > README.md << 'EOF' # Next.js Application Complete Next.js application with CI/CD pipeline. ## Features - βœ… Next.js 14 with App Router - βœ… TypeScript - βœ… Tailwind CSS - βœ… API Routes - βœ… Jest Testing - βœ… PM2 Process Management - βœ… GitHub Actions CI/CD - βœ… Vercel Deployment ## Development npm install npm run dev ## Production npm run build npm start ## Testing npm test ## Deployment Automatically deploys to Vercel on push to main branch. EOF COMMAND_BLOCK: cat > README.md << 'EOF' # Next.js Application Complete Next.js application with CI/CD pipeline. ## Features - βœ… Next.js 14 with App Router - βœ… TypeScript - βœ… Tailwind CSS - βœ… API Routes - βœ… Jest Testing - βœ… PM2 Process Management - βœ… GitHub Actions CI/CD - βœ… Vercel Deployment ## Development npm install npm run dev ## Production npm run build npm start ## Testing npm test ## Deployment Automatically deploys to Vercel on push to main branch. EOF COMMAND_BLOCK: # Install all dependencies npm install # This will take a few minutes COMMAND_BLOCK: # Install all dependencies npm install # This will take a few minutes COMMAND_BLOCK: # Install all dependencies npm install # This will take a few minutes COMMAND_BLOCK: # Build for production npm run build # Expected output: # βœ“ Compiled successfully # βœ“ Linting and checking validity of types # βœ“ Collecting page data # βœ“ Generating static pages COMMAND_BLOCK: # Build for production npm run build # Expected output: # βœ“ Compiled successfully # βœ“ Linting and checking validity of types # βœ“ Collecting page data # βœ“ Generating static pages COMMAND_BLOCK: # Build for production npm run build # Expected output: # βœ“ Compiled successfully # βœ“ Linting and checking validity of types # βœ“ Collecting page data # βœ“ Generating static pages COMMAND_BLOCK: # Build first npm run build # Give Node.js permission to bind to port 80 sudo setcap 'cap_net_bind_service=+ep' $(which node) # Start with PM2 pm2 start ecosystem.config.js # Check status pm2 status # View logs pm2 logs nextjs-app # Save PM2 process list pm2 save # Setup PM2 to start on system boot pm2 startup # Follow the command it outputs COMMAND_BLOCK: # Build first npm run build # Give Node.js permission to bind to port 80 sudo setcap 'cap_net_bind_service=+ep' $(which node) # Start with PM2 pm2 start ecosystem.config.js # Check status pm2 status # View logs pm2 logs nextjs-app # Save PM2 process list pm2 save # Setup PM2 to start on system boot pm2 startup # Follow the command it outputs COMMAND_BLOCK: # Build first npm run build # Give Node.js permission to bind to port 80 sudo setcap 'cap_net_bind_service=+ep' $(which node) # Start with PM2 pm2 start ecosystem.config.js # Check status pm2 status # View logs pm2 logs nextjs-app # Save PM2 process list pm2 save # Setup PM2 to start on system boot pm2 startup # Follow the command it outputs COMMAND_BLOCK: # Test locally curl http://localhost:80 # Or open in browser # http://localhost:80 COMMAND_BLOCK: # Test locally curl http://localhost:80 # Or open in browser # http://localhost:80 COMMAND_BLOCK: # Test locally curl http://localhost:80 # Or open in browser # http://localhost:80 COMMAND_BLOCK: # Initialize git git init # Add all files git add . # Commit git commit -m "Initial commit: Complete Next.js app with CI/CD" # Add remote (replace with your repo) git remote add origin [email protected]:YOUR_USERNAME/nextjs-app.git # Push git push -u origin main COMMAND_BLOCK: # Initialize git git init # Add all files git add . # Commit git commit -m "Initial commit: Complete Next.js app with CI/CD" # Add remote (replace with your repo) git remote add origin [email protected]:YOUR_USERNAME/nextjs-app.git # Push git push -u origin main COMMAND_BLOCK: # Initialize git git init # Add all files git add . # Commit git commit -m "Initial commit: Complete Next.js app with CI/CD" # Add remote (replace with your repo) git remote add origin [email protected]:YOUR_USERNAME/nextjs-app.git # Push git push -u origin main COMMAND_BLOCK: # Create workflow file cat > .github/workflows/deploy.yml << 'EOF' name: CI/CD Pipeline on: push: branches: [main, develop] pull_request: branches: [main] env: NODE_VERSION: '20.x' jobs: test: name: Test runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linter run: npm run lint - name: Run tests run: npm test build: name: Build runs-on: ubuntu-latest needs: test steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Build application run: npm run build - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: nextjs-build path: .next retention-days: 7 deploy: name: Deploy to Vercel runs-on: ubuntu-latest needs: [test, build] if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: - name: Checkout code uses: actions/checkout@v4 - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod' EOF # Commit and push git add .github/workflows/deploy.yml git commit -m "Add GitHub Actions CI/CD workflow" git push origin main COMMAND_BLOCK: # Create workflow file cat > .github/workflows/deploy.yml << 'EOF' name: CI/CD Pipeline on: push: branches: [main, develop] pull_request: branches: [main] env: NODE_VERSION: '20.x' jobs: test: name: Test runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linter run: npm run lint - name: Run tests run: npm test build: name: Build runs-on: ubuntu-latest needs: test steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Build application run: npm run build - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: nextjs-build path: .next retention-days: 7 deploy: name: Deploy to Vercel runs-on: ubuntu-latest needs: [test, build] if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: - name: Checkout code uses: actions/checkout@v4 - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod' EOF # Commit and push git add .github/workflows/deploy.yml git commit -m "Add GitHub Actions CI/CD workflow" git push origin main COMMAND_BLOCK: # Create workflow file cat > .github/workflows/deploy.yml << 'EOF' name: CI/CD Pipeline on: push: branches: [main, develop] pull_request: branches: [main] env: NODE_VERSION: '20.x' jobs: test: name: Test runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linter run: npm run lint - name: Run tests run: npm test build: name: Build runs-on: ubuntu-latest needs: test steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Build application run: npm run build - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: nextjs-build path: .next retention-days: 7 deploy: name: Deploy to Vercel runs-on: ubuntu-latest needs: [test, build] if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: - name: Checkout code uses: actions/checkout@v4 - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod' EOF # Commit and push git add .github/workflows/deploy.yml git commit -m "Add GitHub Actions CI/CD workflow" git push origin main COMMAND_BLOCK: npm install -g vercel COMMAND_BLOCK: npm install -g vercel COMMAND_BLOCK: npm install -g vercel CODE_BLOCK: vercel login 1. Select "Continue with GitHub" 2. Vercel will open a browser window 3. Browser URL: https://vercel.com/auth/github 4. Click "Authorize Vercel" 5. You'll see: "Successfully logged in!" 6. Return to terminal 7. Terminal shows: βœ… Success! GitHub authentication complete CODE_BLOCK: vercel login 1. Select "Continue with GitHub" 2. Vercel will open a browser window 3. Browser URL: https://vercel.com/auth/github 4. Click "Authorize Vercel" 5. You'll see: "Successfully logged in!" 6. Return to terminal 7. Terminal shows: βœ… Success! GitHub authentication complete CODE_BLOCK: vercel login 1. Select "Continue with GitHub" 2. Vercel will open a browser window 3. Browser URL: https://vercel.com/auth/github 4. Click "Authorize Vercel" 5. You'll see: "Successfully logged in!" 6. Return to terminal 7. Terminal shows: βœ… Success! GitHub authentication complete CODE_BLOCK: vercel link CODE_BLOCK: vercel link CODE_BLOCK: vercel link COMMAND_BLOCK: ## Follow These Prompts Exactly: ### Prompt 1: ? Set up "~/nextjs-app"? (Y/n) **Answer:** Type `y` or just press **Enter** (default is Yes) ### Prompt 2 ? Which scope should contain your project? ❯ yourname (Personal Account) your-team (Team Account) **Answer:** Use arrow keys to select your account, then press **Enter** ### Prompt 3 ? Link to existing project? (y/N) **Answer:** Type `n` or just press **Enter** (we're creating new project) ### Prompt 4 ? What's your project's name? (nextjs-app) **Answer:** Press **Enter** (keep default name) or type a custom name ### Prompt 5 ? In which directory is your code located? (./) **Answer:** Press **Enter** (keep default `./`) ## Expected Success Output βœ… Linked to yourname/nextjs-app (created .vercel and added it to .gitignore) COMMAND_BLOCK: ## Follow These Prompts Exactly: ### Prompt 1: ? Set up "~/nextjs-app"? (Y/n) **Answer:** Type `y` or just press **Enter** (default is Yes) ### Prompt 2 ? Which scope should contain your project? ❯ yourname (Personal Account) your-team (Team Account) **Answer:** Use arrow keys to select your account, then press **Enter** ### Prompt 3 ? Link to existing project? (y/N) **Answer:** Type `n` or just press **Enter** (we're creating new project) ### Prompt 4 ? What's your project's name? (nextjs-app) **Answer:** Press **Enter** (keep default name) or type a custom name ### Prompt 5 ? In which directory is your code located? (./) **Answer:** Press **Enter** (keep default `./`) ## Expected Success Output βœ… Linked to yourname/nextjs-app (created .vercel and added it to .gitignore) COMMAND_BLOCK: ## Follow These Prompts Exactly: ### Prompt 1: ? Set up "~/nextjs-app"? (Y/n) **Answer:** Type `y` or just press **Enter** (default is Yes) ### Prompt 2 ? Which scope should contain your project? ❯ yourname (Personal Account) your-team (Team Account) **Answer:** Use arrow keys to select your account, then press **Enter** ### Prompt 3 ? Link to existing project? (y/N) **Answer:** Type `n` or just press **Enter** (we're creating new project) ### Prompt 4 ? What's your project's name? (nextjs-app) **Answer:** Press **Enter** (keep default name) or type a custom name ### Prompt 5 ? In which directory is your code located? (./) **Answer:** Press **Enter** (keep default `./`) ## Expected Success Output βœ… Linked to yourname/nextjs-app (created .vercel and added it to .gitignore) COMMAND_BLOCK: # Get Vercel Token # Go to: https://vercel.com/account/tokens # Click "Create Token" # Name: GitHub Actions # Scope: Full Account # Copy the token # Get Project ID and Org ID cat .vercel/project.json COMMAND_BLOCK: # Get Vercel Token # Go to: https://vercel.com/account/tokens # Click "Create Token" # Name: GitHub Actions # Scope: Full Account # Copy the token # Get Project ID and Org ID cat .vercel/project.json COMMAND_BLOCK: # Get Vercel Token # Go to: https://vercel.com/account/tokens # Click "Create Token" # Name: GitHub Actions # Scope: Full Account # Copy the token # Get Project ID and Org ID cat .vercel/project.json CODE_BLOCK: { "projectId": "prj_abc123xyz", "orgId": "team_xyz789abc" } CODE_BLOCK: { "projectId": "prj_abc123xyz", "orgId": "team_xyz789abc" } CODE_BLOCK: { "projectId": "prj_abc123xyz", "orgId": "team_xyz789abc" } CODE_BLOCK: | Secret Name | Value Description | Where to Get It | |--------------------|-----------------------------|----------------------------------------------| | VERCEL_TOKEN | Your Vercel personal token | https://vercel.com/account/tokens | | VERCEL_ORG_ID | Vercel Organization ID | From `.vercel/project.json` β†’ `cat .vercel/project.json` | | VERCEL_PROJECT_ID | Vercel Project ID | From `.vercel/project.json` β†’ `cat .vercel/project.json` | CODE_BLOCK: | Secret Name | Value Description | Where to Get It | |--------------------|-----------------------------|----------------------------------------------| | VERCEL_TOKEN | Your Vercel personal token | https://vercel.com/account/tokens | | VERCEL_ORG_ID | Vercel Organization ID | From `.vercel/project.json` β†’ `cat .vercel/project.json` | | VERCEL_PROJECT_ID | Vercel Project ID | From `.vercel/project.json` β†’ `cat .vercel/project.json` | CODE_BLOCK: | Secret Name | Value Description | Where to Get It | |--------------------|-----------------------------|----------------------------------------------| | VERCEL_TOKEN | Your Vercel personal token | https://vercel.com/account/tokens | | VERCEL_ORG_ID | Vercel Organization ID | From `.vercel/project.json` β†’ `cat .vercel/project.json` | | VERCEL_PROJECT_ID | Vercel Project ID | From `.vercel/project.json` β†’ `cat .vercel/project.json` | COMMAND_BLOCK: # Make a change echo "# Test deployment" >> README.md # Commit and push git add README.md git commit -m "Test CI/CD pipeline" git push origin main # Watch workflow # Go to: GitHub β†’ Actions tab # Watch the pipeline run: # βœ… Test # βœ… Build # βœ… Deploy to Vercel COMMAND_BLOCK: # Make a change echo "# Test deployment" >> README.md # Commit and push git add README.md git commit -m "Test CI/CD pipeline" git push origin main # Watch workflow # Go to: GitHub β†’ Actions tab # Watch the pipeline run: # βœ… Test # βœ… Build # βœ… Deploy to Vercel COMMAND_BLOCK: # Make a change echo "# Test deployment" >> README.md # Commit and push git add README.md git commit -m "Test CI/CD pipeline" git push origin main # Watch workflow # Go to: GitHub β†’ Actions tab # Watch the pipeline run: # βœ… Test # βœ… Build # βœ… Deploy to Vercel CODE_BLOCK: Local Machine (Ubuntu) ↓ (make changes) ↓ (git commit) ↓ (git push) ↓ GitHub Repository ↓ (triggers) ↓ GitHub Actions (if configured) ↓ (or directly) ↓ Vercel Deployment ↓ (automatic) ↓ Live Website Updated βœ… CODE_BLOCK: Local Machine (Ubuntu) ↓ (make changes) ↓ (git commit) ↓ (git push) ↓ GitHub Repository ↓ (triggers) ↓ GitHub Actions (if configured) ↓ (or directly) ↓ Vercel Deployment ↓ (automatic) ↓ Live Website Updated βœ… CODE_BLOCK: Local Machine (Ubuntu) ↓ (make changes) ↓ (git commit) ↓ (git push) ↓ GitHub Repository ↓ (triggers) ↓ GitHub Actions (if configured) ↓ (or directly) ↓ Vercel Deployment ↓ (automatic) ↓ Live Website Updated βœ… COMMAND_BLOCK: # Navigate to project cd ~/nextjs-app # Edit the home page nano app/page.tsx COMMAND_BLOCK: # Navigate to project cd ~/nextjs-app # Edit the home page nano app/page.tsx COMMAND_BLOCK: # Navigate to project cd ~/nextjs-app # Edit the home page nano app/page.tsx COMMAND_BLOCK: # Check what changed git status # Stage the changes git add app/page.tsx # Commit with a message git commit -m "Update homepage: Changed title and description" # Push to GitHub git push origin main COMMAND_BLOCK: # Check what changed git status # Stage the changes git add app/page.tsx # Commit with a message git commit -m "Update homepage: Changed title and description" # Push to GitHub git push origin main COMMAND_BLOCK: # Check what changed git status # Stage the changes git add app/page.tsx # Commit with a message git commit -m "Update homepage: Changed title and description" # Push to GitHub git push origin main CODE_BLOCK: 1. Open browser 2. Go to: https://vercel.com/dashboard 3. Click on your project: sowmiya-next-js-app 4. You'll see: "Building..." 5. Wait 30-60 seconds 6. Status changes to: "Ready" βœ… 7. Click "Visit" to see your changes live! CODE_BLOCK: 1. Open browser 2. Go to: https://vercel.com/dashboard 3. Click on your project: sowmiya-next-js-app 4. You'll see: "Building..." 5. Wait 30-60 seconds 6. Status changes to: "Ready" βœ… 7. Click "Visit" to see your changes live! CODE_BLOCK: 1. Open browser 2. Go to: https://vercel.com/dashboard 3. Click on your project: sowmiya-next-js-app 4. You'll see: "Building..." 5. Wait 30-60 seconds 6. Status changes to: "Ready" βœ… 7. Click "Visit" to see your changes live! - βœ… Runs on Ubuntu 24 on port 80 as a background service - βœ… Uses TypeScript and Tailwind CSS - βœ… Has automated testing with Jest - βœ… Auto-deploys to Vercel on every push - βœ… Includes GitHub Actions CI/CD pipeline - Next.js 14 (App Router) - Tailwind CSS - PM2 (Process Manager) - GitHub Actions - Ubuntu 24.04 server (local or cloud) - GitHub account - Vercel account (free) - Go to: https://github.com/YOUR_USERNAME/nextjs-app - Click: Settings β†’ Secrets and variables β†’ Actions - Click: "New repository secret" - Add three secrets: