When building modern web applications on AWS, we often need to combine static content delivery with dynamic API processing under the same domain. Here's how to configure CloudFront and API Gateway to work together:
First, set up your CloudFront distribution pointing to your S3 bucket for static assets:
Resources: MyDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Enabled: true DefaultRootObject: index.html Origins: - DomainName: my-bucket.s3.amazonaws.com Id: S3Origin S3OriginConfig: {} DefaultCacheBehavior: TargetOriginId: S3Origin ViewerProtocolPolicy: redirect-to-https AllowedMethods: - GET - HEAD CachedMethods: - GET - HEAD
Create a new API Gateway REST API with Lambda integration for POST requests:
Resources: MyApi: Type: AWS::Serverless::Api Properties: StageName: prod DefinitionBody: swagger: "2.0" paths: /api/submit: post: x-amazon-apigateway-integration: uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SubmitFunction.Arn}/invocations httpMethod: POST type: aws_proxy
Add a custom behavior in CloudFront to route specific paths to API Gateway:
Resources: MyDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: # ... existing config ... CacheBehaviors: - PathPattern: /api/* TargetOriginId: ApiGatewayOrigin AllowedMethods: - GET - POST - PUT - DELETE - HEAD - OPTIONS ForwardedValues: QueryString: true Cookies: Forward: all ViewerProtocolPolicy: redirect-to-https
Set up your API Gateway as a custom origin in CloudFront:
Origins: - DomainName: !Sub ${MyApi}.execute-api.${AWS::Region}.amazonaws.com Id: ApiGatewayOrigin CustomOriginConfig: HTTPPort: 80 HTTPSPort: 443 OriginProtocolPolicy: https-only
Configure appropriate cache settings for your API paths:
CacheBehaviors: - PathPattern: /api/submit TargetOriginId: ApiGatewayOrigin DefaultTTL: 0 MinTTL: 0 MaxTTL: 0
Implement proper security measures for your API endpoints:
Resources: ApiPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Ref SubmitFunction Principal: apigateway.amazonaws.com SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MyApi}/*/*/*
Verify your configuration works correctly:
# Test static content curl https://example.com/index.html # Test API endpoint curl -X POST https://example.com/api/submit -d '{"test":"data"}'
Configure custom error responses in CloudFront:
ErrorPages: - ErrorCode: 404 ResponseCode: 200 ResponsePagePath: /index.html
When building modern web applications, we often need to serve both static content (HTML, CSS, JS) and handle dynamic API requests through the same domain. AWS provides excellent services for each component:
- CloudFront + S3 for static asset delivery
- API Gateway + Lambda for dynamic backend processing
The core problem emerges when we need to:
static.example.com → CloudFront → S3 (GET requests)
static.example.com → API Gateway → Lambda (POST requests)
We can't simply point the domain to both services simultaneously using DNS. Here's the proper approach.
The most elegant solution is to use CloudFront as the single entry point that routes requests based on:
- HTTP method (GET vs POST)
- Path patterns (/api/* vs /*)
- Headers or other request characteristics
Here's how to configure this in AWS:
1. Create API Gateway Endpoint
First, set up your API Gateway with Lambda integration:
// Example Lambda function for API Gateway
exports.handler = async (event) => {
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
2. Configure CloudFront Distribution
Create a CloudFront distribution with:
- S3 origin for static content
- Custom origin pointing to your API Gateway endpoint
3. Set Up Cache Behaviors
Configure path-based routing in CloudFront:
Behaviors:
- Path Pattern: /api/*
Origin: API Gateway
Allowed HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
Cache Policy: CachingDisabled
- Path Pattern: /*
Origin: S3 Bucket
Allowed HTTP Methods: GET, HEAD
Cache Policy: Managed-CachingOptimized
For more complex scenarios:
Lambda@Edge for Dynamic Routing
You can use Lambda@Edge to make routing decisions:
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
// Route POST requests to API Gateway
if (request.method === 'POST') {
request.origin = {
custom: {
domainName: 'your-api-id.execute-api.region.amazonaws.com',
port: 443,
protocol: 'https',
path: '',
sslProtocols: ['TLSv1.2'],
}
};
}
callback(null, request);
};
Handling CORS
Ensure proper CORS configuration for API Gateway:
# CloudFormation snippet for CORS
ApiGatewayMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: OPTIONS
ResourceId: !Ref ApiGatewayResource
RestApiId: !Ref ApiGateway
Integration:
Type: MOCK
RequestTemplates:
application/json: '{"statusCode": 200}'
When implementing this architecture:
- Use regional API endpoints for lower latency
- Configure CloudFront TTLs appropriately for static content
- Enable compression in both CloudFront and API Gateway
- Monitor cache hit ratios in CloudFront
Some frequent challenges include:
- 403 errors from API Gateway - check IAM permissions
- Mixed content warnings - ensure all origins use HTTPS
- Cache invalidation delays - use versioned paths for static assets