Skip to main content

Tech Stack

Detalhamento completo do stack serverless utilizado no Portal do The News.

Frontend

Core Technologies

Next.js 15

React Framework
  • App Router
  • Server Components + Actions
  • Edge Runtime
  • Partial Pre-rendering (PPR)

TypeScript 5.4

Type Safety
  • Strict mode
  • Server/Client separation
  • Database type generation
  • API type safety

TailwindCSS 3.4

Utility-First CSS
  • JIT compilation
  • Design system tokens
  • Dark mode
  • Container queries

Cloudflare Pages

JAMstack Hosting
  • Global edge deployment
  • Instant rollbacks
  • Branch previews
  • Custom domains

Libraries & Tools

{
  "dependencies": {
    "next": "^15.0.0",
    "react": "^18.3.0",
    "react-dom": "^18.3.0",
    "typescript": "^5.4.0",
    "@neondatabase/serverless": "^0.9.0",
    "drizzle-orm": "^0.30.0",
    "next-sanity": "^9.0.0",
    "next-auth": "^4.24.0",
    "@t3-oss/env-nextjs": "^0.10.0",
    "zod": "^3.22.0",
    "tailwindcss": "^3.4.0",
    "framer-motion": "^11.0.0",
    "@radix-ui/react-*": "^1.0.0",
    "lucide-react": "^0.400.0"
  },
  "devDependencies": {
    "drizzle-kit": "^0.20.0",
    "wrangler": "^3.0.0",
    "@cloudflare/workers-types": "^4.0.0"
  }
}

Serverless Backend

Next.js Server-Side

// app/api/users/route.ts
import { db } from '@/lib/db'
import { users } from '@/lib/schema'

export async function GET(request: Request) {
  const allUsers = await db.select().from(users)
  return Response.json(allUsers)
}

export async function POST(request: Request) {
  const body = await request.json()
  const newUser = await db.insert(users).values(body)
  return Response.json(newUser)
}

export const runtime = 'edge'

Databases & Storage

Primary Database

Neon PostgreSQL

Serverless PostgreSQL
  • Branching for development
  • Auto-scaling compute
  • Connection pooling
  • Read replicas
// lib/db.ts
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'

const sql = neon(process.env.DATABASE_URL!)
export const db = drizzle(sql)

Edge Storage

Cloudflare D1

SQLite at Edge
  • Global distribution
  • Zero latency reads
  • SQL interface
  • Perfect for caching
// Edge database for caching
const stmt = env.D1_DB.prepare('SELECT * FROM cache WHERE key = ?')
const result = await stmt.bind(key).first()

Cloudflare KV

Key-Value Store
  • Global edge storage
  • Eventually consistent
  • Simple API
  • Session storage
// Session storage
await env.KV.put(`session:${id}`, JSON.stringify(session), {
  expirationTtl: 3600 // 1 hour
})

Content Management

Sanity.io

Headless CMS
  • Real-time API
  • Image optimization
  • Content relationships
  • GROQ query language
// lib/sanity.ts
import { createClient } from 'next-sanity'

export const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: 'production',
  apiVersion: '2024-01-01',
  useCdn: true
})

// Fetch articles
const articles = await sanity.fetch(`
  *[_type == "article"] {
    title,
    slug,
    publishedAt,
    excerpt,
    "author": author->name
  }
`)

Infrastructure

Cloudflare Stack

Edge Computing

Cloudflare Pages
  • Global edge deployment
  • Instant rollbacks
  • Branch previews
  • Custom domains + SSL

CDN & Security

Cloudflare CDN
  • 330+ locations
  • DDoS protection
  • Web Application Firewall
  • Bot management

External Services

ServicePurposeIntegration
NeonPrimary databaseServerless PostgreSQL
Sanity.ioContent managementHeadless CMS
NextAuth.jsAuthenticationOAuth + JWT
SentryError monitoringSDK integration

Database Schema

Drizzle ORM Setup

// drizzle.config.ts
import type { Config } from 'drizzle-kit'

export default {
  schema: './src/lib/schema.ts',
  out: './drizzle',
  driver: 'pg',
  dbCredentials: {
    connectionString: process.env.DATABASE_URL!,
  },
} satisfies Config

Schema Definition

// lib/schema.ts
import { pgTable, uuid, varchar, timestamp, text, boolean } from 'drizzle-orm/pg-core'

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: varchar('email', { length: 255 }).unique().notNull(),
  name: varchar('name', { length: 255 }),
  avatar: varchar('avatar', { length: 500 }),
  emailVerified: boolean('email_verified').default(false),
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow(),
})

export const articles = pgTable('articles', {
  id: uuid('id').primaryKey().defaultRandom(),
  title: varchar('title', { length: 500 }).notNull(),
  slug: varchar('slug', { length: 200 }).unique().notNull(),
  content: text('content').notNull(),
  excerpt: text('excerpt'),
  published: boolean('published').default(false),
  publishedAt: timestamp('published_at'),
  authorId: uuid('author_id').references(() => users.id),
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow(),
})

Authentication

NextAuth.js Configuration

// lib/auth.ts
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import { DrizzleAdapter } from '@auth/drizzle-adapter'
import { db } from './db'

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: DrizzleAdapter(db),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    session({ session, user }) {
      session.user.id = user.id
      return session
    },
  },
  pages: {
    signIn: '/login',
    signOut: '/logout',
    error: '/auth-error',
  },
})

Development Tools

Local Development

Core Tools

  • pnpm: Fast package manager
  • wrangler: Cloudflare CLI
  • drizzle-kit: Database toolkit
  • next: Framework CLI

VSCode Extensions

  • Tailwind CSS IntelliSense
  • TypeScript Hero
  • Cloudflare Workers
  • Sanity.io
  • Drizzle ORM
  • Error Lens

Scripts Package.json

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "type-check": "tsc --noEmit",
    "db:push": "drizzle-kit push:pg",
    "db:generate": "drizzle-kit generate:pg",
    "db:studio": "drizzle-kit studio",
    "sanity": "sanity dev",
    "cf:dev": "wrangler dev",
    "cf:deploy": "wrangler deploy"
  }
}

Monitoring & Observability

Serverless Monitoring

// Monitoring integrations
const monitoring = {
  analytics: {
    web: "Cloudflare Web Analytics",
    performance: "Vercel Analytics", 
    core_vitals: "Next.js Analytics",
    custom_events: "Cloudflare Workers Analytics"
  },
  
  errors: {
    client_side: "Sentry Browser SDK",
    server_side: "Sentry Next.js",
    edge_runtime: "Sentry Edge Runtime"
  },
  
  performance: {
    database: "Neon Monitoring",
    edge: "Cloudflare Analytics",
    application: "Next.js Speed Insights"
  }
};

Sentry Integration

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
  debug: false,
  replaysOnErrorSampleRate: 1.0,
  replaysSessionSampleRate: 0.1,
  integrations: [
    new Sentry.Replay({
      maskAllText: true,
      blockAllMedia: true,
    }),
  ],
})

Deployment

Environment Variables

# .env.example
# Database
DATABASE_URL="postgresql://user:pass@host/db"

# Cloudflare
CLOUDFLARE_ACCOUNT_ID="..."
CLOUDFLARE_API_TOKEN="..."

# Sanity
SANITY_PROJECT_ID="..."
SANITY_DATASET="production"

# Auth
NEXTAUTH_URL="https://portal.thenewscc.com.br"
NEXTAUTH_SECRET="..."
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."

# Monitoring
SENTRY_DSN="..."

Deploy Pipeline

# .github/workflows/deploy.yml
name: Deploy to Cloudflare Pages

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - run: pnpm install
      - run: pnpm build
      - uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          projectName: portal-thenews
          directory: .next