Serverless and Static Websites
Static Website Hosting with Serverless
Comparison of Static Website Hosting Solutions
| Solution | Advantages | Disadvantages | Use Cases |
|---|---|---|---|
| S3 + CloudFront | Low cost, global CDN acceleration | Requires manual deployment management | Simple static websites |
| Netlify/Vercel | Automated deployment, continuous integration | Vendor lock-in risk | Developer-friendly projects |
| Serverless Framework | Full-stack integration, flexible customization | High configuration complexity | Complex frontend applications |
S3 Static Website Hosting Configuration Example
# serverless.yml
service: static-website
provider:
name: aws
runtime: nodejs14.x
region: us-east-1
resources:
Resources:
WebsiteBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:service}-${self:provider.stage}-website
AccessControl: PublicRead
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: index.html
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName: ${self:resources.Resources.WebsiteBucket.DomainName}
Id: S3Origin
S3OriginConfig: {}
Enabled: true
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
ViewerCertificate:
CloudFrontDefaultCertificate: true
Deployment Command
serverless deploy --stage prod
API Gateway and Frontend Interaction
API Gateway Configuration Key Points
- CORS Configuration
functions:
apiHandler:
handler: handler.apiHandler
events:
- http:
path: api/data
method: get
cors:
origin: '*'
headers:
- Content-Type
- Authorization
allowCredentials: false
- API Gateway Type Selection
- REST API: Suitable for simple RESTful interfaces
- HTTP API: Lower cost (70% cheaper), faster performance
- WebSocket API: Real-time bidirectional communication
Frontend Call Example (React)
import axios from 'axios';
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
setData(response.data);
} catch (error) {
console.error('API request failed:', error);
}
};
Frontend Deployment (Netlify/Vercel)
Netlify Deployment Process
- Install Netlify CLI
npm install -g netlify-cli
- Login and Configure
netlify login
netlify init
- Deployment Command
netlify deploy --prod
Vercel Deployment Advantages
- Automatic Builds: Recognizes build commands in package.json
- Preview Environments: Creates preview URLs for each commit
- Custom Domains: One-click custom domain binding
Vercel Configuration Example (vercel.json)
{
"rewrites": [
{ "source": "/api/(.*)", "destination": "/api-proxy/$1" }
],
"redirects": [
{ "source": "/old-path", "destination": "/new-path", "permanent": true }
]
}
Serverless and Frontend Frameworks
React with Serverless
Integrating Serverless into a React Project
- Create React Application
npx create-react-app my-app
cd my-app
- Install Serverless Components
npm install serverless serverless-finch --save-dev
- Configure serverless.yml
service: react-frontend
provider:
name: aws
runtime: nodejs14.x
region: us-east-1
plugins:
- serverless-finch
custom:
client:
bucketName: react-frontend-${self:provider.stage}
distributionFolder: build
indexDocument: index.html
errorDocument: index.html
- Deployment Command
serverless client deploy
React Component Example
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('/api/data');
setData(response.data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
return (
<div className="App">
{data ? <pre>{JSON.stringify(data, null, 2)}</pre> : 'Loading...'}
</div>
);
}
export default App;
Vue.js with Serverless
Vue CLI Project Integration
- Create Vue Project
vue create my-vue-app
cd my-vue-app
- Configure Serverless Deployment
npm install serverless serverless-finch --save-dev
- Modify vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/vue-app/'
: '/'
};
- serverless.yml Configuration
service: vue-frontend
provider:
name: aws
runtime: nodejs14.x
plugins:
- serverless-finch
custom:
client:
bucketName: vue-frontend-${self:provider.stage}
distributionFolder: dist
indexDocument: index.html
errorDocument: index.html
Vue Component Example
<template>
<div id="app">
<div v-if="loading">Loading...</div>
<div v-else>
<pre>{{ data }}</pre>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
loading: true,
data: null
};
},
async created() {
try {
const response = await axios.get('/api/data');
this.data = response.data;
} catch (error) {
console.error('Error:', error);
} finally {
this.loading = false;
}
}
};
</script>
Angular with Serverless
Angular CLI Project Integration
- Create Angular Application
ng new angular-app
cd angular-app
- Configure Serverless Deployment
npm install serverless serverless-finch --save-dev
- Modify angular.json
"architect": {
"build": {
"options": {
"outputPath": "dist/angular-app"
}
}
}
- serverless.yml Configuration
service: angular-frontend
provider:
name: aws
runtime: nodejs14.x
plugins:
- serverless-finch
custom:
client:
bucketName: angular-frontend-${self:provider.stage}
distributionFolder: dist/angular-app
indexDocument: index.html
errorDocument: index.html
Angular Service Example
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = '/api/data';
constructor(private http: HttpClient) { }
getData(): Observable<any> {
return this.http.get(this.apiUrl);
}
}
Serverless and Real-Time Communication
WebSocket Implementation in Serverless
WebSocket API Gateway Configuration
functions:
connect:
handler: handler.connect
events:
- websocket: $connect
disconnect:
handler: handler.disconnect
events:
- websocket: $disconnect
default:
handler: handler.message
events:
- websocket: $default
Connection Management Example
// Store connection ID on connect
const connections = new Map();
exports.connect = async (event) => {
const connectionId = event.requestContext.connectionId;
connections.set(connectionId, true);
return {
statusCode: 200,
body: 'Connected'
};
};
exports.disconnect = async (event) => {
const connectionId = event.requestContext.connectionId;
connections.delete(connectionId);
return {
statusCode: 200
};
};
Message Broadcasting Example
const AWS = require('aws-sdk');
exports.message = async (event) => {
const body = JSON.parse(event.body);
const message = body.message;
// Broadcast message to all connections
for (const [connectionId, _] of connections.entries()) {
try {
await sendToConnection(connectionId, message, event);
} catch (err) {
console.error(`Failed to send to ${connectionId}:`, err);
connections.delete(connectionId);
}
}
return {
statusCode: 200,
body: 'Message sent'
};
};
async function sendToConnection(connectionId, message, event) {
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
});
return apigwManagementApi.postToConnection({
ConnectionId: connectionId,
Data: JSON.stringify({ message })
}).promise();
}
Server-Sent Events (SSE) Practice
SSE Implementation Example
exports.handler = async (event) => {
// Set SSE response headers
const headers = {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
};
// Check if it's an SSE request
if (event.httpMethod === 'GET' && event.path === '/events') {
// Keep connection open
const response = {
statusCode: 200,
headers,
body: '',
isBase64Encoded: false
};
// Simulate streaming data
const interval = setInterval(() => {
const data = JSON.stringify({ time: new Date().toISOString() });
// In a real environment, send via WebSocket or other mechanism
console.log(`data: ${data}\n\n`);
}, 1000);
// Simplified handling for Serverless environment
return response;
}
return {
statusCode: 404,
body: 'Not Found'
};
};
Frontend SSE Client
const eventSource = new EventSource('/api/events');
eventSource.onmessage = (e) => {
const data = JSON.parse(e.data);
console.log('New event:', data);
};
eventSource.onerror = (err) => {
console.error('EventSource failed:', err);
eventSource.close();
};
Real-Time Data Push Optimization
Optimization Strategies
- Connection Reuse
// Use a single WebSocket connection for multiple message types
const socket = new WebSocket('wss://api.example.com/ws');
function sendMessage(type, data) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type, data }));
}
}
- Message Batching
let messageQueue = [];
let isSending = false;
async function processQueue() {
if (isSending || messageQueue.length === 0) return;
isSending = true;
const batch = messageQueue.splice(0, 10); // Max 10 messages per batch
try {
await sendBatch(batch);
} finally {
isSending = false;
processQueue(); // Process next batch
}
}
function enqueueMessage(message) {
messageQueue.push(message);
processQueue();
}
- Heartbeat Mechanism
// Client-side heartbeat detection
let pingInterval;
function startHeartbeat() {
pingInterval = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: 'ping' }));
}
}, 30000); // Every 30 seconds
}
function stopHeartbeat() {
clearInterval(pingInterval);
}
- Reconnection Logic
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
function connect() {
const socket = new WebSocket('wss://api.example.com/ws');
socket.onclose = () => {
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
setTimeout(connect, 1000 * Math.pow(2, reconnectAttempts)); // Exponential backoff
}
};
// Other event handlers...
}
connect();
- Data Compression
// Compress messages with gzip
const pako = require('pako');
function compressMessage(message) {
const jsonString = JSON.stringify(message);
return pako.deflate(jsonString, { to: 'string' });
}
function sendMessage(socket, message) {
const compressed = compressMessage(message);
socket.send(compressed);
}
- Smart Routing
// Route messages based on type
function handleMessage(event) {
const data = JSON.parse(event.body);
switch(data.type) {
case 'chat':
return handleChatMessage(data);
case 'notification':
return handleNotification(data);
default:
return { statusCode: 400, body: 'Unknown message type' };
}
}



