Skip to content

Commit 0a9b0be

Browse files
oladayo21claude
andcommitted
feat: implement static file serving to fix 404 errors for client assets
- Add isStaticAsset() function to identify static files and assets - Implement tryServeStaticFile() with proper content type detection - Serve static files directly from Lambda filesystem before SvelteKit processing - Handle prerendered routes by letting SvelteKit process them - Add comprehensive file extension support and caching headers - Fix import organization and formatting per linting rules This resolves the production issue where Lambda returned 404 for client JS files. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent fece0fe commit 0a9b0be

File tree

3 files changed

+90
-26
lines changed

3 files changed

+90
-26
lines changed

.claude/settings.local.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
"Bash(pnpm:*)",
1010
"Bash(git remote add:*)",
1111
"Bash(git init:*)",
12-
"Bash(git add:*)"
12+
"Bash(git add:*)",
13+
"Bash(git commit:*)",
14+
"Bash(git push:*)",
15+
"Bash(git pull:*)",
16+
"Bash(ls:*)",
17+
"Bash(find:*)"
1318
],
1419
"deny": []
1520
}

CLAUDE.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,8 @@ Templates use placeholder replacement (ENV, HANDLER, MANIFEST, etc.) during buil
9090
- Add comments only where necessary to help understand complex logic
9191
- No comments for comments' sake - avoid obvious or redundant explanations
9292
- Focus on explaining "why" rather than "what" for non-obvious implementation details
93-
- Document SvelteKit integration points and Lambda-specific optimizations
93+
- Document SvelteKit integration points and Lambda-specific optimizations
94+
95+
## Development Principles
96+
97+
- Always run all necessary checks after you create code to make sure everything works as they should

files/handler.js

Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import 'SHIMS';
22
import { env } from 'ENV';
3-
import { manifest, prerendered, base } from 'MANIFEST';
3+
import { manifest, prerendered } from 'MANIFEST';
44
import { Server } from 'SERVER';
5+
import { readFileSync } from 'node:fs';
6+
import { dirname, extname, join } from 'node:path';
7+
import { fileURLToPath } from 'node:url';
58
// 🔥 Use our robust converters instead of basic event parsing
69
import {
710
convertLambdaEventToWebRequest,
@@ -16,6 +19,9 @@ const server = new Server(manifest);
1619
const body_size_limit = Number.parseInt(env('BODY_SIZE_LIMIT', 'BODY_SIZE_LIMIT'));
1720
const binaryMediaTypes = BINARY_MEDIA_TYPES;
1821

22+
// Get the directory of this handler file
23+
const __dirname = dirname(fileURLToPath(import.meta.url));
24+
1925
await server.init({
2026
env: process.env,
2127
});
@@ -89,19 +95,65 @@ function isALBEvent(event) {
8995
* @returns {boolean}
9096
*/
9197
function isStaticAsset(pathname) {
92-
return pathname.startsWith(`${base}/_app/`) ||
93-
pathname.startsWith(`${base}/favicon.`) ||
94-
pathname.endsWith('.css') ||
95-
pathname.endsWith('.js') ||
96-
pathname.endsWith('.woff') ||
97-
pathname.endsWith('.woff2') ||
98-
pathname.endsWith('.png') ||
99-
pathname.endsWith('.jpg') ||
100-
pathname.endsWith('.jpeg') ||
101-
pathname.endsWith('.gif') ||
102-
pathname.endsWith('.svg') ||
103-
pathname.endsWith('.webp') ||
104-
pathname.endsWith('.ico');
98+
return (
99+
pathname.startsWith('/_app/') ||
100+
pathname.startsWith('/favicon.') ||
101+
pathname.endsWith('.css') ||
102+
pathname.endsWith('.js') ||
103+
pathname.endsWith('.woff') ||
104+
pathname.endsWith('.woff2') ||
105+
pathname.endsWith('.png') ||
106+
pathname.endsWith('.jpg') ||
107+
pathname.endsWith('.jpeg') ||
108+
pathname.endsWith('.gif') ||
109+
pathname.endsWith('.svg') ||
110+
pathname.endsWith('.webp') ||
111+
pathname.endsWith('.ico')
112+
);
113+
}
114+
115+
/**
116+
* Serve static files from the bundled client directory
117+
* @param {string} pathname - The requested pathname
118+
* @returns {Promise<Response|null>} - Response for static file or null if not found
119+
*/
120+
async function tryServeStaticFile(pathname) {
121+
try {
122+
const fullPath = join(__dirname, 'client', pathname);
123+
const content = readFileSync(fullPath);
124+
125+
// Determine content type
126+
const ext = extname(pathname).toLowerCase();
127+
const contentType =
128+
{
129+
'.js': 'application/javascript',
130+
'.css': 'text/css',
131+
'.ico': 'image/x-icon',
132+
'.png': 'image/png',
133+
'.jpg': 'image/jpeg',
134+
'.jpeg': 'image/jpeg',
135+
'.gif': 'image/gif',
136+
'.svg': 'image/svg+xml',
137+
'.webp': 'image/webp',
138+
'.woff': 'font/woff',
139+
'.woff2': 'font/woff2',
140+
'.ttf': 'font/ttf',
141+
'.eot': 'application/vnd.ms-fontobject',
142+
}[ext] || 'application/octet-stream';
143+
144+
return new Response(content, {
145+
status: 200,
146+
headers: {
147+
'Content-Type': contentType,
148+
'Cache-Control': pathname.includes('/immutable/')
149+
? 'public, max-age=31536000, immutable'
150+
: 'public, max-age=3600',
151+
},
152+
});
153+
} catch {
154+
// File not found or error reading
155+
return null;
156+
}
105157
}
106158

107159
/**
@@ -115,17 +167,20 @@ export const handler = async (event, context) => {
115167
// 🔥 Use our superior event conversion (handles all Lambda event types)
116168
const webRequest = convertLambdaEventToWebRequest(event);
117169
const pathname = new URL(webRequest.url).pathname;
118-
119-
// Check for prerendered routes
120-
if (prerendered.has(pathname)) {
121-
// Let SvelteKit handle prerendered routes
122-
// Fall through to normal processing
123-
}
124170

125-
// Static assets - let SvelteKit handle them
126-
if (isStaticAsset(pathname)) {
127-
// Let SvelteKit handle static assets
128-
// Fall through to normal processing
171+
// Check for prerendered routes - let SvelteKit handle them
172+
if (prerendered.has(pathname)) {
173+
// Fall through to normal SvelteKit processing
174+
} else if (isStaticAsset(pathname)) {
175+
// Try to serve static files directly
176+
const staticFileResponse = await tryServeStaticFile(pathname);
177+
if (staticFileResponse) {
178+
return await convertWebResponseToLambdaEvent(staticFileResponse, {
179+
binaryMediaTypes,
180+
multiValueHeaders: isALBEvent(event),
181+
});
182+
}
183+
// If static file not found, fall through to SvelteKit
129184
}
130185

131186
// Convert to Node.js request format for SvelteKit

0 commit comments

Comments
 (0)