Serverless Deployments with DeployHQ: AWS Lambda, Vercel, and Netlify

Devops & Infrastructure, Node, and Tutorials

Serverless Deployments with DeployHQ: AWS Lambda, Vercel, and Netlify

Serverless computing has revolutionized how we deploy applications, eliminating server management while enabling automatic scaling. Whether you're deploying to AWS Lambda, Vercel, or Netlify, DeployHQ can streamline your serverless deployment workflow. In this guide, you'll learn how to integrate serverless platforms with your existing deployment pipeline.

Understanding Serverless Deployments

Serverless doesn't mean no servers—it means you don't manage them. The platform handles infrastructure, scaling, and availability. Your focus shifts entirely to code and business logic.

flowchart LR
    subgraph "Traditional"
        T1[Code] --> T2[Build]
        T2 --> T3[Configure Server]
        T3 --> T4[Deploy]
        T4 --> T5[Scale Manually]
    end

    subgraph "Serverless"
        S1[Code] --> S2[Build]
        S2 --> S3[Deploy Function]
        S3 --> S4[Auto-Scale]
    end

If you're new to modern web architecture, check out our guide on JAMStack vs. Traditional CMS.

AWS Lambda Deployment with DeployHQ

AWS Lambda is the most mature serverless platform. Here's how to deploy Lambda functions using DeployHQ's build pipeline.

Project Structure

serverless-app/
├── src/
│   ├── handlers/
│   │   ├── api.js
│   │   ├── webhook.js
│   │   └── scheduler.js
│   └── lib/
│       └── database.js
├── package.json
├── serverless.yml
└── .deployhq/
    └── deploy.sh

Serverless Framework Configuration

# serverless.yml
service: my-serverless-api

provider:
  name: aws
  runtime: nodejs20.x
  region: ${env:AWS_REGION, 'us-east-1'}
  environment:
    DATABASE_URL: ${env:DATABASE_URL}
    API_KEY: ${env:API_KEY}

functions:
  api:
    handler: src/handlers/api.handler
    events:
      - http:
          path: /{proxy+}
          method: any
    timeout: 30
    memorySize: 512

  webhook:
    handler: src/handlers/webhook.handler
    events:
      - http:
          path: /webhook
          method: post

  scheduler:
    handler: src/handlers/scheduler.handler
    events:
      - schedule: rate(1 hour)

plugins:
  - serverless-offline

package:
  exclude:
    - node_modules/**
    - .git/**
    - tests/**
  include:
    - src/**

DeployHQ Build Commands

# Install dependencies
npm ci

# Run tests
npm test

# Package and deploy with Serverless Framework
npx serverless deploy --stage ${DEPLOY_ENVIRONMENT:-production}

# Output deployed endpoints
npx serverless info --stage ${DEPLOY_ENVIRONMENT:-production}

Environment Variables Setup

In DeployHQ project settings, add these environment variables:

AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=xxx
AWS_REGION=us-east-1
DATABASE_URL=postgresql://...
API_KEY=xxx

For secure credential management, see our guide on protecting your API keys.

Vercel Deployment Integration

Vercel excels at deploying Next.js applications and serverless functions. While Vercel has its own Git integration, you can also deploy via DeployHQ for more control.

Project Structure for Vercel

nextjs-app/
├── pages/
│   ├── api/
│   │   ├── users.js
│   │   └── products.js
│   ├── index.js
│   └── about.js
├── components/
├── public/
├── package.json
├── vercel.json
└── next.config.js

Vercel Configuration

// vercel.json
{
  "version": 2,
  "builds": [
    {
      "src": "package.json",
      "use": "@vercel/next"
    }
  ],
  "routes": [
    {
      "src": "/api/(.*)",
      "dest": "/api/$1"
    }
  ],
  "env": {
    "DATABASE_URL": "@database_url",
    "NEXT_PUBLIC_API_URL": "@api_url"
  }
}

DeployHQ Build Commands for Vercel

# Install Vercel CLI
npm install -g vercel

# Install project dependencies
npm ci

# Run tests
npm test

# Build Next.js
npm run build

# Deploy to Vercel
vercel deploy --prod --token $VERCEL_TOKEN

# Or deploy preview for non-main branches
if [ "$DEPLOY_BRANCH" != "main" ]; then
    vercel deploy --token $VERCEL_TOKEN
else
    vercel deploy --prod --token $VERCEL_TOKEN
fi

Serverless API Route Example

// pages/api/users.js
import { db } from '../../lib/database';

export default async function handler(req, res) {
  const { method } = req;

  switch (method) {
    case 'GET':
      try {
        const users = await db.user.findMany({
          take: 50,
          orderBy: { createdAt: 'desc' }
        });
        res.status(200).json(users);
      } catch (error) {
        res.status(500).json({ error: 'Failed to fetch users' });
      }
      break;

    case 'POST':
      try {
        const user = await db.user.create({
          data: req.body
        });
        res.status(201).json(user);
      } catch (error) {
        res.status(400).json({ error: 'Failed to create user' });
      }
      break;

    default:
      res.setHeader('Allow', ['GET', 'POST']);
      res.status(405).end(`Method ${method} Not Allowed`);
  }
}

For Next.js deployment specifics, see our guide on deploying a Next.js application.

Netlify Functions Deployment

Netlify offers a simpler serverless experience with automatic function deployment.

Project Structure

netlify-app/
├── netlify/
│   └── functions/
│       ├── hello.js
│       └── submit-form.js
├── src/
│   └── index.html
├── package.json
└── netlify.toml

Netlify Configuration

# netlify.toml
[build]
  command = "npm run build"
  publish = "dist"
  functions = "netlify/functions"

[build.environment]
  NODE_VERSION = "20"

[functions]
  directory = "netlify/functions"

[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200

Netlify Function Example

// netlify/functions/submit-form.js
const { createClient } = require('@supabase/supabase-js');

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_KEY
);

exports.handler = async (event, context) => {
  if (event.httpMethod !== 'POST') {
    return {
      statusCode: 405,
      body: JSON.stringify({ error: 'Method not allowed' })
    };
  }

  try {
    const data = JSON.parse(event.body);

    // Validate input
    if (!data.email || !data.message) {
      return {
        statusCode: 400,
        body: JSON.stringify({ error: 'Missing required fields' })
      };
    }

    // Save to database
    const { error } = await supabase
      .from('submissions')
      .insert([data]);

    if (error) throw error;

    return {
      statusCode: 200,
      body: JSON.stringify({ message: 'Form submitted successfully' })
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Internal server error' })
    };
  }
};

DeployHQ Build Commands for Netlify

# Install Netlify CLI
npm install -g netlify-cli

# Install dependencies
npm ci

# Build site
npm run build

# Deploy to Netlify
netlify deploy --prod --auth $NETLIFY_AUTH_TOKEN --site $NETLIFY_SITE_ID

Edge Functions: The Next Evolution

Edge functions run closer to your users, reducing latency. Both Vercel and Netlify support them.

flowchart TB
    subgraph "Traditional Lambda"
        U1[User in Tokyo] --> R1[us-east-1]
        R1 --> U1
    end

    subgraph "Edge Functions"
        U2[User in Tokyo] --> E1[Tokyo Edge]
        E1 --> U2
    end

    style E1 fill:#90EE90
    style R1 fill:#FFB6C1

Vercel Edge Function Example

// pages/api/geo.js
export const config = {
  runtime: 'edge',
};

export default function handler(request) {
  const { geo } = request;

  return new Response(
    JSON.stringify({
      country: geo?.country || 'Unknown',
      city: geo?.city || 'Unknown',
      region: geo?.region || 'Unknown',
    }),
    {
      status: 200,
      headers: {
        'Content-Type': 'application/json',
      },
    }
  );
}

Netlify Edge Function Example

// netlify/edge-functions/geo.js
export default async (request, context) => {
  const { country, city } = context.geo;

  return new Response(
    JSON.stringify({
      country: country?.name || 'Unknown',
      city: city || 'Unknown',
    }),
    {
      headers: {
        'Content-Type': 'application/json',
      },
    }
  );
};

export const config = {
  path: '/api/geo',
};

Deployment Pipeline Flow

sequenceDiagram
    participant Dev as Developer
    participant DHQ as DeployHQ
    participant Build as Build Server
    participant Platform as Serverless Platform

    Dev->>DHQ: git push
    DHQ->>Build: Trigger build
    Build->>Build: npm install
    Build->>Build: npm test
    Build->>Build: npm run build
    Build->>Platform: Deploy (CLI)
    Platform->>Platform: Validate
    Platform->>Platform: Deploy functions
    Platform->>Build: Success + URLs
    Build->>DHQ: Complete
    DHQ->>Dev: Notification

Environment Management

Managing environments in serverless requires careful configuration:

// config/index.js
const environments = {
  development: {
    apiUrl: 'http://localhost:3000/api',
    databaseUrl: process.env.DEV_DATABASE_URL,
  },
  staging: {
    apiUrl: 'https://staging-api.example.com',
    databaseUrl: process.env.STAGING_DATABASE_URL,
  },
  production: {
    apiUrl: 'https://api.example.com',
    databaseUrl: process.env.PROD_DATABASE_URL,
  },
};

const environment = process.env.NODE_ENV || 'development';
export const config = environments[environment];

For more on environment management, see managing multiple environments with DeployHQ.

Cold Starts and Performance

Cold starts are the biggest challenge in serverless. Mitigate them with:

Keep Functions Warm

// AWS Lambda warmer
exports.handler = async (event) => {
  // Check if this is a warming call
  if (event.source === 'serverless-plugin-warmup') {
    console.log('Warming up!');
    return 'Warmed!';
  }

  // Normal handler logic
  return await handleRequest(event);
};

Optimize Bundle Size

// package.json
{
  "dependencies": {
    // Use lighter alternatives
    "date-fns": "^2.30.0"  // Instead of moment.js
  },
  "devDependencies": {
    "esbuild": "^0.19.0"   // Fast bundler
  }
}

Connection Pooling

// lib/database.js
import { PrismaClient } from '@prisma/client';

// Reuse connection across invocations
let prisma;

if (process.env.NODE_ENV === 'production') {
  prisma = new PrismaClient();
} else {
  // Prevent too many connections in development
  if (!global.prisma) {
    global.prisma = new PrismaClient();
  }
  prisma = global.prisma;
}

export { prisma };

Monitoring and Debugging

AWS CloudWatch Integration

// Structured logging for CloudWatch
const log = (level, message, data = {}) => {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    level,
    message,
    ...data,
    requestId: process.env.AWS_REQUEST_ID,
  }));
};

exports.handler = async (event) => {
  log('info', 'Request received', { path: event.path });

  try {
    const result = await processRequest(event);
    log('info', 'Request completed', { statusCode: 200 });
    return result;
  } catch (error) {
    log('error', 'Request failed', { error: error.message });
    throw error;
  }
};

Best Practices Summary

  1. Keep functions small and focused on single tasks
  2. Minimize cold starts with proper initialization
  3. Use environment variables for configuration
  4. Implement proper error handling with structured logging
  5. Test locally before deploying (serverless-offline, netlify dev)
  6. Monitor performance and set up alerts
  7. Use edge functions for latency-sensitive operations
  8. Bundle dependencies efficiently

Getting Started

Ready to go serverless? Here's your action plan:

  1. Choose your platform (Lambda, Vercel, Netlify)
  2. Set up project structure
  3. Configure DeployHQ build commands
  4. Add environment variables
  5. Deploy and monitor

For traditional deployments, see our CI/CD pipeline guide.


Questions about serverless deployments? Contact support@deployhq.com or follow @deployhq for the latest guides.

A little bit about the author

Alex is a content specialist on the DeployHQ team, focused on helping developers streamline their deployment workflows. With a background in DevOps and technical writing, Alex enjoys breaking down complex topics into actionable guides for the DeployHQ and DeployBot communities.