Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
npm install @aws-sdk/client-lambda// lib/lambda.ts
import { LambdaClient } from "@aws-sdk/client-lambda";
export const lambdaClient = new LambdaClient({
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});// lib/invoke-lambda.ts
import { InvokeCommand } from "@aws-sdk/client-lambda";
import { lambdaClient } from "./lambda";
export async function invokeLambda<TInput, TOutput>(
functionName: string,
payload: TInput
): Promise<TOutput> {
const command = new InvokeCommand({
FunctionName: functionName,
Payload: new TextEncoder().encode(JSON.stringify(payload)),
});
const response = await lambdaClient.send(command);
if (response.FunctionError) {
const errorPayload = JSON.parse(
new TextDecoder().decode(response.Payload)
);
throw new Error(
`Lambda error: ${errorPayload.errorMessage ?? response.FunctionError}`
);
}
return JSON.parse(new TextDecoder().decode(response.Payload)) as TOutput;
}When to reach for this: You need to invoke AWS Lambda functions from a Next.js application for background processing, heavy computation, or accessing AWS-native services.
// app/components/ImageProcessor.tsx
"use client";
import { useState } from "react";
import { processImage } from "../actions/image-actions";
export default function ImageProcessor() {
const [result, setResult] = useState<{
thumbnailUrl: string;
dimensions: { width: number; height: number };
} | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleProcess(formData: FormData) {
setLoading(true);
setError(null);
try {
const data = await processImage(formData);
setResult(data);
} catch (err) {
setError(err instanceof Error ? err.message : "Processing failed");
} finally {
setLoading(false);
}
}
return (
<div className="max-w-md mx-auto p-6 space-y-4">
<form action={handleProcess}>
<input name="imageUrl" placeholder="Image URL" required className="w-full border rounded px-3 py-2" />
<input name="width" type="number" placeholder="Target width" defaultValue={200} className="w-full border rounded px-3 py-2 mt-2" />
<button
type="submit"
disabled={loading}
className="mt-3 bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50"
>
{loading ? "Processing..." : "Process Image"}
</button>
</form>
{error && <p className="text-red-600">{error}</p>}
{result && (
<div>
<p>Thumbnail: {result.dimensions.width}x{result.dimensions.height}</p>
<img src={result.thumbnailUrl} alt="Processed thumbnail" className="border rounded" />
</div>
)}
</div>
);
}// app/actions/image-actions.ts
"use server";
import { invokeLambda } from "@/lib/invoke-lambda";
interface ProcessImageInput {
imageUrl: string;
targetWidth: number;
}
interface ProcessImageOutput {
thumbnailUrl: string;
dimensions: { width: number; height: number };
}
export async function processImage(formData: FormData) {
const imageUrl = formData.get("imageUrl") as string;
const targetWidth = Number(formData.get("width")) || 200;
const result = await invokeLambda<ProcessImageInput, ProcessImageOutput>(
"image-processor",
{ imageUrl, targetWidth }
);
return result;
}What this demonstrates:
InvokeCommand sends the payload as a Uint8Array and receives the response the same wayFunctionError field on the response indicates if the Lambda function threw an errorAsync (fire-and-forget) invocation:
import { InvokeCommand, InvocationType } from "@aws-sdk/client-lambda";
const command = new InvokeCommand({
FunctionName: "email-sender",
InvocationType: InvocationType.Event, // async, returns 202 immediately
Payload: new TextEncoder().encode(
JSON.stringify({ to: "user@example.com", subject: "Welcome!" })
),
});
await lambdaClient.send(command); // Returns immediately, no response payloadDry run (validate permissions and input):
const command = new InvokeCommand({
FunctionName: "my-function",
InvocationType: InvocationType.DryRun,
Payload: new TextEncoder().encode(JSON.stringify({ test: true })),
});
// Returns 204 if valid, throws if permissions are wrong
await lambdaClient.send(command);List available functions:
import { ListFunctionsCommand } from "@aws-sdk/client-lambda";
const command = new ListFunctionsCommand({ MaxItems: 50 });
const response = await lambdaClient.send(command);
const functions = response.Functions?.map((fn) => ({
name: fn.FunctionName,
runtime: fn.Runtime,
memory: fn.MemorySize,
lastModified: fn.LastModified,
}));Invoke with qualifier (version or alias):
const command = new InvokeCommand({
FunctionName: "my-function",
Qualifier: "production", // alias name or version number
Payload: new TextEncoder().encode(JSON.stringify(payload)),
});InvokeCommandOutput.Payload is Uint8Array | undefined - always check for undefined before decodingInvocationType enum provides RequestResponse (sync), Event (async), and DryRunimport type { InvokeCommandOutput } from "@aws-sdk/client-lambda";
function decodePayload<T>(response: InvokeCommandOutput): T | null {
if (!response.Payload) return null;
return JSON.parse(new TextDecoder().decode(response.Payload)) as T;
}Payload encoding - Passing a plain string or object as Payload throws a type error. Fix: Always encode with new TextEncoder().encode(JSON.stringify(data)) and decode responses with new TextDecoder().decode(response.Payload).
Cold start latency - First invocations after idle periods take several seconds. Fix: Use provisioned concurrency for latency-sensitive functions, or accept cold starts and show loading states in the UI.
Timeout mismatch - Lambda functions default to 3 seconds, but processing may take longer. Fix: Set the Lambda function timeout appropriately in AWS. Also consider async invocation for long-running tasks.
Silent failures with async invocation - InvocationType: "Event" returns 202 without error details. Fix: Set up a Dead Letter Queue (DLQ) on the Lambda function to capture failed async invocations.
Payload size limit - Synchronous payloads are limited to 6MB. Fix: For larger data, store it in S3 and pass the S3 key in the payload.
Missing permissions - The IAM user or role needs lambda:InvokeFunction permission. Fix: Add the permission to your IAM policy for the specific function ARN.
| Library | Best For | Trade-off |
|---|---|---|
| @aws-sdk/client-lambda | Direct Lambda invocation | Requires AWS setup, IAM permissions |
| Next.js API Routes | Server-side logic in same app | Limited execution time, shared resources |
| Vercel Functions | Serverless on Vercel | Vercel-specific, less control |
| AWS API Gateway + Lambda | Public API endpoints | More infrastructure to manage |
| Step Functions | Orchestrating multiple Lambdas | Higher complexity, additional cost |
InvokeCommand expects Payload as a Uint8Array, not a plain object or stringnew TextEncoder().encode(JSON.stringify(data)) to convertUint8Array and need new TextDecoder().decode()InvocationType: "Event"): Returns HTTP 202 immediately, no response payloadconst response = await lambdaClient.send(command);
if (response.FunctionError) {
const error = JSON.parse(
new TextDecoder().decode(response.Payload)
);
throw new Error(error.errorMessage);
}The FunctionError field is set when the Lambda throws; the payload contains error details.
InvocationType: "Event" returns 202 without error details even if the function failsasync function invokeLambda<TInput, TOutput>(
functionName: string,
payload: TInput
): Promise<TOutput> {
const command = new InvokeCommand({
FunctionName: functionName,
Payload: new TextEncoder().encode(JSON.stringify(payload)),
});
const response = await lambdaClient.send(command);
return JSON.parse(
new TextDecoder().decode(response.Payload)
) as TOutput;
}Payload is typed as Uint8Array | undefinedundefined before decodingas T for your expected return typeconst command = new InvokeCommand({
FunctionName: "my-function",
Qualifier: "production", // alias or version number
Payload: new TextEncoder().encode(JSON.stringify(payload)),
});lambda:InvokeFunction permissionlambda:ListFunctions if you also need to list available functions"use server";
import { invokeLambda } from "@/lib/invoke-lambda";
export async function processImage(formData: FormData) {
const imageUrl = formData.get("imageUrl") as string;
return invokeLambda("image-processor", { imageUrl });
}Server Actions keep AWS credentials on the server and provide a clean API for client components.
InvocationType: "DryRun" validates permissions and input without executing the functionReviewed by Chris St. John·Last updated Jul 10, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥