Mastering Serverless and Next.js: A Quick and Easy Manual Configuration

The SaaS Enthusiast
4 min readJan 21, 2024

Are you juggling with Serverless and Next.js for your front-end data storage needs? You’re not alone. This article demystifies the complexities of configuring Serverless as a backend and integrating it with Next.js using Amplify. Say goodbye to days of confusion and hello to getting everything up and running in just minutes!

Using Serverless Framework to create a bucket

Setting up a bucket is straightforward with a dedicated s3.yml file. For instance, you might begin with a simple declaration like this:

Resources:
BacaBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: ${self:custom.myUniqueBucketName}

Then, link this in your serverless.yml file:

Next, update the IAM role statements to grant necessary permissions:

iamRoleStatements:
# S3 Permissions
- Effect: "Allow"
Action:
- "s3:PutObject"
- "s3:GetObject"
Resource: "arn:aws:s3:::${self:custom.bacaBucketName}/*"

To use this bucket name in a lambda function, include it in the environment references:

provider:
# ... other provider configurations ...
environment:
# ... other environment variables ...
BACA_BUCKET_NAME: ${self:custom.bacaBucketName}

Access the bucket in your lambda with this reference:

const bucketName = process.env.BACA_BUCKET_NAME;
// Utilize bucketName for S3 tasks

CORS

CORS, or Cross-Origin Resource Sharing, is essential for enabling requests to your resources from different domains. Update your s3.yml to include a CORS setup like this:

Resources:
BacaBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: ${self:custom.bacaBucketName}
CorsConfiguration:
CorsRules:
- AllowedOrigins:
- "*"
AllowedMethods:
- "GET"
- "PUT"
- "POST"
- "DELETE"
- "HEAD"
AllowedHeaders:
- "*"
MaxAge: 3000

This configuration ensures that your bucket resources can be accessed safely and efficiently from various web applications.

S3 Streams Triggering a Lambda Function

Imagine needing to start text extraction from an uploaded image and saving the results in a DynamoDB table for later querying. In such scenarios, triggering a Lambda function directly from S3 events is ideal. Here’s how you set it up in serverless.yml:

on-image-upload:
handler: functions/supporters/on-image-upload.handler
events:
- s3:
bucket: ${self:custom.myveryUniqueLambda}
event: s3:ObjectCreated:*
existing: true

This method is beneficial when immediate processing of the uploaded file is needed. Comparatively, using an SQS to trigger a Lambda introduces a queuing system, which can be advantageous for managing high-volume or delayed processing, offering improved control and scalability.

Configuring the Bucket on Next.js: To handle image uploads in Next.js, configure your bucket details in various environment files like .env.local, .env.dev, and .env.prod. Here's an example for local development:

AWS_S3_BUCKET_NAME=dev-my-very-unique-bucket-name
AWS_S3_REGION=us-east-1

Read these configurations in an awsconfig.js file:

export const amplifyConfig = {
Auth: {
// Auth info
},
Storage: {
region: process.env.AWS_S3_REGION,
bucket: process.env.AWS_S3_BUCKET_NAME,
},
API: {
// API info
}
}

Initialize AWS in your _app.js:

Amplify.configure(amplifyconfig);

For file uploads, create a useStorageStore using AWS Amplify JS SDK:

import create from 'zustand';
import { Storage } from 'aws-amplify';
const useStorageStore = create((set) => ({
loading: false,
error: null,
uploadFile: async ({selectedFile}) => {
set({ loading: true, error: null });
try {
const result = await Storage.put("fileName.jpeg, selectedFile, {
contentType: selectedFile.type,
level: 'protected',
});
} catch (error) {
console.log('Error uploading file: ', error);
set({ loading: false, error });
throw error;
}
},
}));

export default useStorageStore;

You can then utilize this method in any component for file uploads:

const { uploadFile } = useStorageStore();
// Additional component code
await uploadFile({ selectedFile});

Final Thoughts

In the evolving landscape of web development, the integration of Serverless architecture with Next.js offers a potent combination for managing data storage and processing needs. This approach not only streamlines development workflows but also harnesses the power of cloud computing to scale applications effortlessly.

Why This Approach is Important

This method is crucial for several reasons. Firstly, it leverages the strengths of Serverless computing, such as scalability, cost-effectiveness, and reduced operational overhead. By offloading server management and capacity planning to cloud providers, developers can focus on writing code that adds direct value to their applications. Furthermore, the use of Next.js with Amplify facilitates a seamless front-end experience, enabling quick deployment and efficient data handling.

Considerations and Downsides

However, this approach isn’t without its downsides. One significant consideration is the complexity of configuration and management. Setting up Serverless functions, S3 buckets, and ensuring proper integration with Next.js can be daunting, especially for those new to these technologies.

Another critical aspect is the cost. While Serverless can be cost-effective at scale, unpredictable workloads can lead to fluctuating costs. It’s essential to monitor and manage these expenses, keeping an eye on the number of Lambda function triggers and data transfer costs associated with S3.

Pricing and Lambda Triggers

Pricing is primarily based on the number of executions, execution time, and the resources allocated to the Lambda functions. It’s important to optimize function code to reduce execution time and resources. Additionally, managing the frequency of Lambda triggers is crucial; unnecessary triggers can increase costs and may lead to throttling if they exceed the service limits.

Error Handling and Management

Error handling is another critical factor. Serverless architecture can introduce unique challenges in debugging and error tracking due to its distributed nature. Implementing comprehensive logging and monitoring strategies is essential to quickly identify and resolve issues. This includes tracking errors in Lambda executions, handling failed S3 operations, and ensuring robust error responses in the Next.js application.

In Summary

While the integration of Serverless, S3, and Next.js presents an innovative solution for modern web applications, it requires careful consideration of configuration complexities, cost management, and error handling strategies. When executed correctly, it provides a scalable, efficient, and cost-effective way to build and manage web applications, but it demands a thorough understanding of the underlying technologies and a proactive approach to monitoring and optimization. This integration, therefore, represents a significant step forward in web development, but like any advanced solution, it requires skill and diligence to harness its full potential.

--

--