Rift Rewind: My experience (CloudFront + Amplify + Lambda)
Its a globally distributed Riot ID site that uses s3/CloudFront + Amplify for the frontend, and a Python Lambda for the Riot API key. Some animated UI and cool additions.
Live site: https://main.dd6ixce0j7nk4.amplifyapp.com
Source Repo: https://github.com/Vinay-Vimalkumar/rift-rewind-project
TLDR:
I had built a Riot ID insights site with using Amplify Hosting (global + CI/CD), a secure lambda backend that works with the Riot API key, and the secrets in the SSM Parameter store so I can rotate the dev keys and don't have to redeploy it every time. The UI uses AOS/Motion animations, skeleton loaders, OP.GG deeplinks, mastery progress bars, KPI's, and a lot more for recent matches. I hit a real world issue and fixed them: 400 bad body, 3s Lambda timeout, browser CORS "failed to fetch", 404 Riot ID not found (region mix-ups), and a 401 expired dev keys. Below includes all of the problems and fixes.

Architecture:
1
2
3
4
5
6
7
8
Browser (Amplify-hosted)
├─ index.html / script.js (animations, skeletons, presets)
└─ POST JSON → Lambda Function URL (CORS on URL)
├─ SSM Parameter Store: /rift-rewind/riot-api-key (SecureString)
└─ Riot APIs:
• /riot/account/v1/accounts/by-riot-id/{game}/{tag} (routing: americas/europe/asia/sea) → PUUID
• /lol/summoner/v4/summoners/by-puuid/{puuid} (platform: na1/kr/euw1/…)
• /lol/champion-mastery/v4/champion-masteries/by-puuid/{puuid}/top?count=3
Why this?
- Amplify allows for global hosting, CI/CD, and no manual invalidations
- Lambda allows to keep the riot key out of the browser and connect the Riot flow
- SSM Parameter Store allows us to rotate the keys instantly without changing any of the code
- Riot PUUID flow allows a correct and stable compared to legacy by calling names
Day by Day
Day 1 -
What I did
- Created my free tier account
- Enabled MFA on root and stopped using it
- Created IAM admin user for daily work
- Set the budget to $5 with the email alerts
Problem and fix
Problem: I started with the root and without any spend on guardrails
Fix: I switched to the IAM admin and enabled MFA on root while adding a budget alert before touching any of the services
Problem: I started with the root and without any spend on guardrails
Fix: I switched to the IAM admin and enabled MFA on root while adding a budget alert before touching any of the services
Day 2 -
What I did
- Created an S3 bucket with Block Public Access turned on
- Uploaded a sample file to prove that I could store the assets
- Confirmed "AccessDenied" on the object URL
Problem and fix
Problem: I had thought the site was broken because the S3 object URL displayed a 403 error
Fix: I kept the bucket private and served file publicly using a CDN/hosting layer aka Amplify instead of opening the bucket
Problem: I had thought the site was broken because the S3 object URL displayed a 403 error
Fix: I kept the bucket private and served file publicly using a CDN/hosting layer aka Amplify instead of opening the bucket

Day 3 -
What I shipped
- AOS with fade/slide + motion for more subtle and fast transitions
- Skeleton loaders in order to avoid layout jumps
- Pro presets for Faker, Chovy, Ruler so user can have an easy demo
- OP.GG deep link to see the region and a copy PUUID button
- Mastery progress bars made from points until the next level
- KPI's recent winrate, avg KDA, and average game duration
- "Load more" recent matches recomputes the KPI's
- An auto cleanup with pruneEmptyTiles() so no empty cards appear
- Explicit empty states such as unranked and no recent matches
Problem:
Problem 1: There was a wonky layout with 2 of the empty boxes and inconsistent recent match rows
Fix: I added pruneEmptyTiles() to remove the ghost tiles, normalized recent match row, and added clear empty states
Problem 1: There was a wonky layout with 2 of the empty boxes and inconsistent recent match rows
Fix: I added pruneEmptyTiles() to remove the ghost tiles, normalized recent match row, and added clear empty states
Problem 2: The champions icons occasionally had a 404 error due to the Data Dragon version drift
Fix: You fetch https://ddragon.leagueoflegends.com/api/versions.json at runtime and used for icon URLs with a static fallback
Fix: You fetch https://ddragon.leagueoflegends.com/api/versions.json at runtime and used for icon URLs with a static fallback

Day 4 -
What I did
- Connected My GitHub repo to AWS Amplify Hosting
- Every push to main builds and deploys globally
- There is an optional custom domain via Route 53
Problem and fix
Problem: Early manual tests caused a 404 error on direct loads
Fix: Made a single page static entry (index.html) and moved it to Amplify which handles routing + cache by default
Problem: Early manual tests caused a 404 error on direct loads
Fix: Made a single page static entry (index.html) and moved it to Amplify which handles routing + cache by default

Day 5:
What I did
- Let Amplify handle end to end builds with a cache invalidation
- Centralized the backend URL in HTML for convenience:
1
https://ddragon.leagueoflegends.com/api/versions.json
- Cache busted JS with
script.js?v=12
Problem and fix
Problem A: Latest UI was not showing the stale client cache
Fix: Let Amplify handle all the invalidations by adding
Problem A: Latest UI was not showing the stale client cache
Fix: Let Amplify handle all the invalidations by adding
?v= to JS includeProblem B: The frontend did not know the function URL in the early iterations
Fix: Single source of truth with
Fix: Single source of truth with
window.__RIOT_API_URL__ in index.html that script.js reads

Day 6:
What I built
Python lambda that
Python lambda that
- Parses body defensively:
json.loads(event.get("body") or "{}") - validates GameName#TAG
- Uses a routing region for
accounts/by-riot-idto PUUID - Uses platform region for the summoner and mastery
- Fetches the RIOT key from SSM
/rift-rewind/riot-api-keywhich is a SecureString - Returns a stable JSON for UI
CORS
- Enable CORS on the function URL and allow POST + OPTIONS
- Do NOT duplicate the CORS headers inside of code responses
Verification
1
2
3
4
curl -X POST \
-H "Content-Type: application/json" \
-d '{"summonerName":"Hide on bush#KR1","region":"kr"}' \
https://anbqqriwd2ysbwxyquqspqg7ee0xwrcp.lambda-url.us-east-1.on.aws/
Returns the level + top 3 mastery
Problems
- 400 "summoner name is required" RequestID
Cause: An empty/malformed test body
Fix: A defensive parse + UI validation using Riot ID format: GameName#Tag - Timeout: "Task timed out after 3 seconds"
Fix: Increased lambda timeout to 10s - Browser: "Network error: Failed to fetch"
Cause: CORS preflight was being blocked by conflicting configs
Fix: CORS relies on the function URL being POST and OPTIONS so removed other headers - 404 Riot ID not found for the known pros
Cause: region kept mixing up between the routing and platform regions
Fix: A strict routing map for account call and platform region for the rest - 401 Unauthorized
Cause: Expired Riot dev key
Fix: Update the SSM/rift-rewind/riot-api-keywith new key without needing to redeploy - Ranked blank
Cause: Many of pro ID's are Unranked
Fix: Render a clean Unranked state

Day 7 -
Finaly editing
- Smoother motions and timings
- Recent matches card fixed
- Unranked display fixed
- All the empty tiles removed
Rotate without redeploying
I stored my Riot key in SSM parameter store under
I stored my Riot key in SSM parameter store under
/rift-rewind/riot-api-key and when it expires I just overwrite it and since Lambda reads it at runtime the UI works after a page refreshWhat I'd build next
- Full match history with queue/role filters
- Cache Data Dragon data
- Favorites + simple auth
- Route 53 custom domain + SSL
- Lighter weight response caching to reduce Riot calls
Troubleshooting Matrix (quick copy)
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 Unauthorized | Riot dev key expired | Update SSM /rift-rewind/riot-api-key with a new key (SecureString) |
| “Failed to fetch” | CORS/preflight issue | Function URL: allow POST + OPTIONS; remove code-side CORS headers |
| 404 Riot ID not found | Routing vs platform region mismatch | Use americas/europe/asia/sea for accounts/by-riot-id; na1/kr/euw1… for summoner/mastery |
| 400 “summoner name is required” | Empty/invalid request body | Defensive parse server-side + UI validation for GameName#TAG |
| 3s timeout | Default Lambda timeout too low | Increase timeout to 10s |
| Ranked blank | Player is actually Unranked | Render explicit Unranked state |
Live site: https://main.dd6ixce0j7nk4.amplifyapp.com
Source Repo: https://github.com/Vinay-Vimalkumar/rift-rewind-project
If this saved you time please consider leaving a like and commenting! Let me know if you have any questions or need any help.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article