Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.thenewscc.com.br/llms.txt

Use this file to discover all available pages before exploring further.

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