<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Erik Hanchett</title>
    <description>The latest articles on DEV Community by Erik Hanchett (@erikch).</description>
    <link>https://dev.to/erikch</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1994%2F0j84YwMs.jpeg</url>
      <title>DEV Community: Erik Hanchett</title>
      <link>https://dev.to/erikch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/erikch"/>
    <language>en</language>
    <item>
      <title>5 Things to Do After Installing OpenClaw (Before You Break It)</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Mon, 11 May 2026 18:51:25 +0000</pubDate>
      <link>https://dev.to/erikch/5-things-to-do-after-installing-openclaw-before-you-break-it-1153</link>
      <guid>https://dev.to/erikch/5-things-to-do-after-installing-openclaw-before-you-break-it-1153</guid>
      <description>&lt;p&gt;I've been running OpenClaw for a while now and I think it's pretty awesome.&lt;/p&gt;

&lt;p&gt;If you haven't heard of it, &lt;a href="https://openclaw.ai/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; is a self-hosted AI agent that runs on your own hardware: a spare laptop, a Mac Mini, or a VPS. At AWS we have &lt;a href="https://aws.amazon.com/lightsail/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Lightsail&lt;/a&gt; which is a great option for running OpenClaw on a dedicated instance. (Full disclosure: I'm a Developer Advocate for AWS, so I'll be recommending AWS services where relevant. Feel free to use whatever VPS provider you'd like.) It's not a chatbot you open in a browser tab. It's always on. It connects to your messaging apps (Telegram, Discord, WhatsApp), reads your files, runs scheduled tasks, and takes action on your behalf. By default, a heartbeat runs every 30 minutes, it checks for instructions in &lt;code&gt;HEARTBEAT.md&lt;/code&gt; and acts proactively. You can customize the interval or disable it entirely, but it's on out of the box.&lt;/p&gt;

&lt;p&gt;The whole thing is built around plain-text markdown files. Your agent's personality lives in &lt;code&gt;SOUL.md&lt;/code&gt;. Its long-term memory lives in &lt;code&gt;MEMORY.md&lt;/code&gt;. Its knowledge of you lives in &lt;code&gt;USER.md&lt;/code&gt;. These files are your agent. &lt;/p&gt;

&lt;p&gt;The default install is not entirely production ready. Your gateway might be exposed to the public internet. Your default model might be eating your API budget. And without a few key configuration steps, your agent has no idea who you are or what it's allowed to do.&lt;/p&gt;

&lt;p&gt;These are the five things that I would do after you install OpenClaw.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;FYI:&lt;/strong&gt; OpenClaw moves fast — updates ship 2–3 times a week and the UI changes regularly. The steps here are based on v2026.5.x. If something looks different in your version, check the &lt;a href="https://docs.openclaw.ai/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; first. And as always, be careful giving any agent access to your files, accounts, and messaging apps. Start with read-only tasks and expand permissions as you build trust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Opening the Dashboard&lt;/li&gt;
&lt;li&gt;1. Lock Down Your Gateway&lt;/li&gt;
&lt;li&gt;2. Switch to a Cheaper Model&lt;/li&gt;
&lt;li&gt;3. Write Your SOUL.md With Boundaries&lt;/li&gt;
&lt;li&gt;4. Seed Your Memory Files&lt;/li&gt;
&lt;li&gt;5. Back Up Your Workspace and Use /new&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OpenClaw installed and running&lt;/li&gt;
&lt;li&gt;Access to your API provider dashboard (Anthropic, OpenAI, or similar)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Opening the Dashboard
&lt;/h2&gt;

&lt;p&gt;The dashboard runs at &lt;code&gt;http://127.0.0.1:18789&lt;/code&gt; by default. The easiest way to open it with your token already attached is to run this from the terminal on the machine where OpenClaw is installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens the dashboard in your browser with a clean URL. If it asks for a token, run this to get it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw config get gateway.auth.token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then paste it into the dashboard login prompt. See the &lt;a href="https://docs.openclaw.ai/web/dashboard" rel="noopener noreferrer"&gt;dashboard docs&lt;/a&gt; for more detail.&lt;/p&gt;

&lt;p&gt;If you've already logged in before, just go straight to &lt;code&gt;http://127.0.0.1:18789&lt;/code&gt;. Your browser should have the session cached.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Lock Down Your Gateway
&lt;/h3&gt;

&lt;p&gt;Good news: the default install gets this right. Bind mode is &lt;code&gt;loopback&lt;/code&gt; and auth mode is &lt;code&gt;token&lt;/code&gt; out of the box. If you installed via the quick-start script, verify this. Some install paths skip token setup during onboarding.&lt;/p&gt;

&lt;p&gt;The reason this is still Step 1 is that it's easy to accidentally break, and most people never verify it. &lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;Settings → Infrastructure&lt;/strong&gt; and confirm these two settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gateway Bind Mode:&lt;/strong&gt; should be &lt;code&gt;loopback&lt;/code&gt; for a local setup. This controls who can reach the port at all — &lt;code&gt;loopback&lt;/code&gt; means only &lt;code&gt;127.0.0.1&lt;/code&gt;, so nothing outside the machine can connect regardless of auth settings. &lt;code&gt;lan&lt;/code&gt; binds to all network interfaces, which is fine on a home machine where your interfaces are private. On a VPS where the network interface has a public IP, &lt;code&gt;lan&lt;/code&gt; puts the port on the internet. That's where the real exposure happens. If you need remote access, use &lt;code&gt;tailnet&lt;/code&gt; (Tailscale) rather than &lt;code&gt;lan&lt;/code&gt; — it keeps the port off the public internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gateway Auth Mode:&lt;/strong&gt; should be &lt;code&gt;token&lt;/code&gt;. This controls what happens once someone reaches the port. If it's &lt;code&gt;none&lt;/code&gt;, anyone who can reach the port connects with no credentials at all. On &lt;code&gt;loopback&lt;/code&gt; that's low risk. On a public bind it's a disaster. There are thousands of OpenClaw instances exposed on the public internet right now, most of them are the result of changing bind mode without realizing what that exposes.&lt;/p&gt;

&lt;p&gt;While you're here, check two more things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Insecure Control UI Auth Toggle:&lt;/strong&gt; may be enabled from your initial setup. Turn it off. When on (&lt;code&gt;allowInsecureAuth: true&lt;/code&gt;), it lets localhost Control UI sessions bypass device identity checks in non-secure HTTP contexts, it's a compatibility workaround that shouldn't be left on in normal use. The security audit will flag it if it's enabled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control UI Allowed Origins:&lt;/strong&gt; should list your specific origins (e.g., &lt;code&gt;http://localhost:18789&lt;/code&gt;). Never set this to &lt;code&gt;*&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To verify from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw config get gateway.auth.mode
&lt;span class="c"&gt;# should print: token&lt;/span&gt;

openclaw config get gateway.bind
&lt;span class="c"&gt;# should print: loopback&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If either prints something different, fix it in the dashboard before moving on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run the security audit.&lt;/strong&gt; OpenClaw has a built-in security audit command. Run it now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw security audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a more thorough check that includes live probes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw security audit &lt;span class="nt"&gt;--deep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it flags issues, &lt;code&gt;--fix&lt;/code&gt; applies the safe defaults automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw security audit &lt;span class="nt"&gt;--fix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then tell your agent to keep checking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Read https://docs.openclaw.ai/gateway/security, run &lt;span class="sb"&gt;`&lt;/span&gt;openclaw security audit &lt;span class="nt"&gt;--deep&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;,
and send me a summary of any findings. Then schedule yourself to repeat this twice a day.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pin your version.&lt;/strong&gt; OpenClaw ships updates 2–3 times a week and some of them break things. The community built a tracker: &lt;a href="https://isitstable.com/openclaw" rel="noopener noreferrer"&gt;isitstable.com/openclaw&lt;/a&gt;. Check it before you update, then install the recommended stable release:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw update &lt;span class="nt"&gt;--tag&lt;/span&gt; &amp;lt;stable-version&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles the doctor check and gateway restart automatically. If you need to roll back to an older version, the manual fallback is &lt;code&gt;npm i -g openclaw@&amp;lt;version&amp;gt;&lt;/code&gt; followed by &lt;code&gt;openclaw doctor&lt;/code&gt; and &lt;code&gt;openclaw gateway restart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When you're ready to update, check the tracker, read the changelog, then update during a quiet period.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Switch to a Cheaper Model
&lt;/h3&gt;

&lt;p&gt;Someone in the community went from $47/week to $6/week with two changes. It's one of the most upvoted posts in &lt;a href="https://www.reddit.com/r/openclaw/comments/1rp8t9r/" rel="noopener noreferrer"&gt;r/openclaw&lt;/a&gt;. That's the whole story for this step.&lt;/p&gt;

&lt;p&gt;The default model on most OpenClaw installs is whatever was set during onboarding, often a top-tier model like GPT-5 or Opus. That's great for setup,  you want the best reasoning when configuring your workspace files and personality. For daily use it works fine too, but it gets expensive fast. Your agent is checking calendars, drafting short replies, and running heartbeat tasks every 30 minutes. None of that needs frontier-model reasoning, and you're paying frontier-model prices for all of it.&lt;/p&gt;

&lt;p&gt;Here's a breakdown of what the community recommends based on your budget and hardware:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5txfrh2203j754jbone.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5txfrh2203j754jbone.png" alt="Description of different models to use" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want the best results and don't want to think about cost, Opus with Sonnet as fallback is the standard recommendation. Just be warned. It's expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cheapest option: use your ChatGPT Plus subscription.&lt;/strong&gt; If you're already paying $20/month for ChatGPT Plus, you can connect OpenClaw to it via OpenAI Codex OAuth. That means you're using your existing subscription instead of paying separately per token. OpenAI explicitly supports this for third-party tools like OpenClaw. To set it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw onboard &lt;span class="nt"&gt;--auth-choice&lt;/span&gt; openai-codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or if you're already past onboarding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw models auth login &lt;span class="nt"&gt;--provider&lt;/span&gt; openai-codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens an OAuth flow to sign in with your ChatGPT account. Once connected, set your model to &lt;code&gt;openai/gpt-5.5&lt;/code&gt; and OpenClaw automatically uses the Codex subscription runtime. Usage limits apply. Check &lt;a href="https://openai.com/chatgpt/pricing/" rel="noopener noreferrer"&gt;OpenAI's plan comparison&lt;/a&gt; for current details on what Plus vs Pro covers.&lt;/p&gt;

&lt;p&gt;Note: Anthropic does allow Claude subscription auth (Claude Pro or Max) via Claude CLI reuse — run &lt;code&gt;openclaw models auth login --provider anthropic --method cli --set-default&lt;/code&gt;. For production use, an API key is more reliable.&lt;/p&gt;

&lt;p&gt;If you want to keep bills low without going local, &lt;strong&gt;Minimax M2.7 via API&lt;/strong&gt; is worth knowing about. It's not as capable as Opus or Sonnet but it's significantly cheaper and the community rates it as surprisingly good for most daily tasks.&lt;/p&gt;

&lt;p&gt;If your machine has 32GB+ RAM, local models become viable and can bring your API costs close to zero for routine work. A common setup: Opus or Sonnet for planning and complex reasoning, then a local model like &lt;strong&gt;Qwen Coder&lt;/strong&gt; (8B parameter, free to run) for the actual code generation. On Apple Silicon (Mac Mini, MacBook Pro) the unified memory means RAM alone is enough for good performance on smaller models. On a PC or Linux box, a dedicated GPU makes a big difference, without one, local models run on CPU and can be slow enough to be frustrating for interactive use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where to change it:&lt;/strong&gt; First, make sure your API key is set. The cleanest way is to add it to &lt;code&gt;~/.openclaw/.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.openclaw/.env&lt;/span&gt;
&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-ant-...
&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenClaw picks this up automatically at startup. Make sure this file isn't committed to a public repo, it contains your credentials.&lt;/p&gt;

&lt;p&gt;Then go to &lt;strong&gt;Settings → AI &amp;amp; Agents → Models tab&lt;/strong&gt; and edit the Model JSON field directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"anthropic/claude-sonnet-4-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fallback"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"anthropic/claude-haiku-4-5"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fallback kicks in if the primary is rate-limited or unavailable. Hit &lt;strong&gt;Save → Apply&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The SOUL.md trick:&lt;/strong&gt; The second change that made the $47→$6 difference was adding one line to SOUL.md:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Only use a more powerful model when I explicitly ask for deep analysis.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets the agent self-route. Routine tasks stay cheap. You ask for "deep analysis" when you actually need it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check your costs:&lt;/strong&gt; Go to &lt;strong&gt;Control → Usage&lt;/strong&gt; in the sidebar. It shows total tokens, cost, and a breakdown by model. Check it daily in your first week until you understand your baseline. Keep an eye on the cache hit rate, a high rate means you're not paying full price for repeated context, since cached tokens cost significantly less than fresh input tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Write Your SOUL.md With Boundaries
&lt;/h3&gt;

&lt;p&gt;SOUL.md is the character sheet for your agent. It defines personality, voice, temperament, and what it's allowed to do. Most people fill it in during onboarding with vague answers and never touch it again. That's a mistake.&lt;/p&gt;

&lt;p&gt;The file lives in your OpenClaw workspace alongside a handful of other markdown files: &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;MEMORY.md&lt;/code&gt;, &lt;code&gt;USER.md&lt;/code&gt;, &lt;code&gt;IDENTITY.md&lt;/code&gt;. Together these plain-text files are your agent. If you ever migrate to new hardware, these files are what you bring with you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tw412gn8o1gmpti76ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tw412gn8o1gmpti76ck.png" alt="OpenClaw workspace files" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;Agent&lt;/strong&gt; in the sidebar and click the &lt;strong&gt;SOUL&lt;/strong&gt; button, under &lt;strong&gt;Files&lt;/strong&gt;. The default template is already there and it's actually pretty good. What it doesn't have is your specific boundaries. Add these at the bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Identity&lt;/span&gt;
You are [name]. You are a collaborator, not an assistant.
Push back when I'm wrong. Challenge ideas during brainstorms.
When I'm heads-down, keep it crisp.

&lt;span class="gu"&gt;## Defaults&lt;/span&gt;
Only use a more powerful model when I explicitly ask for deep analysis.
For routine tasks, use the cheapest capable model.

&lt;span class="gu"&gt;## Boundaries&lt;/span&gt;
Never delete files without explicit confirmation.
Never send messages on my behalf without showing me the draft first.
Never make purchases or API calls that cost money without asking.
If a task fails twice with the same error, stop and report. Do not retry.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last section is easy to skip. Without explicit boundaries, your agent will make reasonable-sounding decisions that you didn't actually authorize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The aggressive note-taker trick:&lt;/strong&gt; Add this to both SOUL.md and AGENTS.md:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an aggressive note-taker. After every significant session,
write key decisions, preferences, and facts into your daily log.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SOUL.md gets it so the behavior is part of the agent's core identity. AGENTS.md gets it so it's enforced as an operating rule. Together they make it stick. Without both, the agent may follow the instruction in some sessions but not others. This is the single biggest thing you can do to prevent memory degradation over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Seed Your Memory Files
&lt;/h3&gt;

&lt;p&gt;The biggest complaint from people who've been using OpenClaw for a few weeks is that it starts forgetting things. The experience gets less magical and more frustrating as you keep re-explaining context it should already know.&lt;/p&gt;

&lt;p&gt;This is a known problem in the community and it's not fully solved. But it's manageable if you set things up right from the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seed MEMORY.md before your first real session.&lt;/strong&gt; Go to &lt;strong&gt;Agent&lt;/strong&gt; in the sidebar, then click the &lt;strong&gt;MEMORY&lt;/strong&gt; button. On a fresh install it shows as "MISSING". Clicking it opens an empty editor and saving creates the file.&lt;/p&gt;

&lt;p&gt;MEMORY.md is for curated, distilled facts the agent needs to remember across every session: standing rules, key decisions, strong preferences. It gets injected into the context window on every turn, so keep it lean. This is not a journal or a log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Memory&lt;/span&gt;

&lt;span class="gu"&gt;## Preferences&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Communication style: direct and concise, skip filler phrases
&lt;span class="p"&gt;-&lt;/span&gt; Response length: brief by default, thorough when I ask for detail
&lt;span class="p"&gt;-&lt;/span&gt; Don't ask clarifying questions for simple tasks. Just do it.

&lt;span class="gu"&gt;## Standing Rules&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Always show me a draft before sending anything externally (email, messages, posts)
&lt;span class="p"&gt;-&lt;/span&gt; Ask before making calendar changes or bookings
&lt;span class="p"&gt;-&lt;/span&gt; If a task fails twice with the same error, stop and report. Don't keep retrying.
&lt;span class="p"&gt;-&lt;/span&gt; Never modify config files without telling me what you're changing and why

&lt;span class="gu"&gt;## Key Context&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Main tools I use: [e.g. VS Code, GitHub, Notion, Slack]
&lt;span class="p"&gt;-&lt;/span&gt; Current focus: [e.g. shipping a feature, writing content, job search]
&lt;span class="p"&gt;-&lt;/span&gt; Things I'm working on this month: [1-3 active projects]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep MEMORY.md short. If it gets too long, OpenClaw truncates it when injecting into context, which means the stuff at the bottom silently disappears. Detailed notes belong in &lt;code&gt;memory/YYYY-MM-DD.md&lt;/code&gt; daily logs, not here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;USER.md is different.&lt;/strong&gt; Click the &lt;strong&gt;USER&lt;/strong&gt; button on the same page. This is the agent's profile of you as a person: your background, role, working style, what you care about. The agent builds this over time, but seeding it upfront saves weeks of it figuring things out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# User Profile&lt;/span&gt;

&lt;span class="gu"&gt;## Who I Am&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Name: [your name]
&lt;span class="p"&gt;-&lt;/span&gt; Role: [what you do, e.g. "frontend engineer at a fintech startup"]
&lt;span class="p"&gt;-&lt;/span&gt; Location / timezone: [city, UTC offset]
&lt;span class="p"&gt;-&lt;/span&gt; Working hours: [e.g. 9am–6pm weekdays]

&lt;span class="gu"&gt;## How I Work&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; I prefer [async / real-time] communication
&lt;span class="p"&gt;-&lt;/span&gt; I get annoyed when: [e.g. things are over-explained, asked obvious questions]
&lt;span class="p"&gt;-&lt;/span&gt; I work best when: [e.g. given context upfront, shown options not just answers]

&lt;span class="gu"&gt;## What I'm Trying to Do With OpenClaw&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [e.g. automate my morning briefing, manage email triage, track projects]
&lt;span class="p"&gt;-&lt;/span&gt; My biggest time sink right now: [what you want help with most]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The short version: USER.md is who you are. MEMORY.md is what the agent must remember. AGENTS.md is the agent's operating procedure: how it behaves, what it does on startup, routing rules. All three get injected into every session, so they all matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable Dreaming.&lt;/strong&gt; Go to your OpenClaw dashboard, find the Dreaming tab, and toggle it on. This is a beta feature with two phases: a shallow phase that writes session summaries to &lt;code&gt;DREAMS.md&lt;/code&gt;, and a deep phase that promotes the strongest memories into &lt;code&gt;MEMORY.md&lt;/code&gt; as permanent long-term storage. If you enable it and don't see things showing up in MEMORY.md right away, that's normal. Only the deep phase promotes to permanent memory, and it runs on its own schedule. Without Dreaming, session logs accumulate but never get distilled into anything the agent reliably recalls.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Back Up Your Workspace and Use /new
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How sessions actually work.&lt;/strong&gt; Every message you send in a session gets included in every future API call in that session. After a few hours of chatting, you're sending thousands of tokens of old conversation with every new message. It costs money, and it makes your agent slower and more confused as the context gets cluttered.&lt;/p&gt;

&lt;p&gt;OpenClaw handles this in two ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compaction&lt;/strong&gt; happens automatically when the context window fills up. It summarizes older conversation history into a condensed entry to free up space. You can also trigger it manually with &lt;code&gt;/compact&lt;/code&gt; before a long task, or guide what it focuses on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/compact Focus on the deployment task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;/new&lt;/code&gt;&lt;/strong&gt; starts a completely fresh session. Your agent keeps everything: SOUL.md, MEMORY.md, USER.md, all your workspace files. You're just clearing the conversation buffer. Use it when you're switching topics or starting a new task. Many people in the community report meaningful cost savings just from using &lt;code&gt;/new&lt;/code&gt; regularly. Most people don't know it exists.&lt;/p&gt;

&lt;p&gt;Also learn &lt;code&gt;/btw&lt;/code&gt;. It fires off a side question without touching your main session context. Great for quick lookups mid-task without opening a whole new session.&lt;/p&gt;

&lt;p&gt;The key rule: anything important needs to be written to files, not just said in chat. Session memory dies on compaction. If you told your agent something three days ago in conversation, it may not remember it. If it's in MEMORY.md, it will.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Back up your workspace files.&lt;/strong&gt; Your agent's entire state (personality, memory, projects, tools) lives in markdown files in the OpenClaw workspace directory. If something goes wrong (and eventually something will), you want to be able to roll back. Make sure to use a &lt;strong&gt;private&lt;/strong&gt; repo. Just ask your agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Help me put my OpenClaw workspace files onto a private GitHub repo
and commit changes automatically after each session.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This takes about ten minutes and has saved people from complete resets when their agent went off the rails or a config change broke something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check Control → Usage daily in your first week.&lt;/strong&gt; The usage panel shows total tokens, cost, and a breakdown by model. You're looking for two things: your baseline daily cost, and your cache hit rate. A high cache hit rate means you're not paying full price for repeated context. That's healthy. If costs are higher than expected, check whether your context profile is set too large under &lt;strong&gt;Settings → AI &amp;amp; Agents → Agent Defaults → Bootstrap Max Chars&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After the first week you'll have a sense of what normal looks like. Set a budget alert at 2x your daily baseline so you get notified if something starts running away. A misconfigured heartbeat task or a loop in an automation can burn through tokens fast without any visible output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;a href="https://aws.amazon.com/lightsail/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Try Amazon Lightsail&lt;/a&gt; — run OpenClaw on a dedicated VPS&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openclaw.ai/" rel="noopener noreferrer"&gt;OpenClaw docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openclaw.ai/gateway/security" rel="noopener noreferrer"&gt;OpenClaw security audit docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://clawhub.openclaw.ai/" rel="noopener noreferrer"&gt;ClawHub skills marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://isitstable.com/openclaw" rel="noopener noreferrer"&gt;Is it stable?&lt;/a&gt; — check before you update&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/oOCN30ulVyo?si=EM4_BcRy53Opf8-t" rel="noopener noreferrer"&gt;Tina's OpenClaw setup guide&lt;/a&gt; — the video that inspired a lot of this post&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;These five steps take a bit of time to do right. The config changes (gateway, model, SOUL.md) take about 30 minutes. Getting your memory files properly seeded and GitHub backup set up will take a bit longer, but it's worth doing before your first real session.&lt;/p&gt;

&lt;p&gt;Let me know in the comments if I missed anything. I'm still learning this thing too.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>aiagents</category>
      <category>selfhosted</category>
      <category>aws</category>
    </item>
    <item>
      <title>Build Your Own AI Butler - A Scheduled Agent That Runs Itself!</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Wed, 06 May 2026 16:00:47 +0000</pubDate>
      <link>https://dev.to/aws/build-your-own-ai-butler-a-scheduled-agent-that-runs-itself-3dmk</link>
      <guid>https://dev.to/aws/build-your-own-ai-butler-a-scheduled-agent-that-runs-itself-3dmk</guid>
      <description>&lt;p&gt;I want an AI agent that works for me. I want it to search up the latest news, and I want it to deliver it to me in a daily message. I want to chat with it, and get advice. I want to expand its capabilities in the future. And most importantly I want to control it. Basically, I want Jarvis.&lt;/p&gt;

&lt;p&gt;Yes Jarvis, the fictional AI character in the Marvel Cinematic Universe (MCU), serving as Tony Stark's butler. I've never had my own agentic butler, and now that I think about it who wouldn't? 😆 A few years ago I would have told you this would have never happened. But today with the rise of agents the future is here.&lt;/p&gt;

&lt;p&gt;Well sort of... we are getting closer than ever though!&lt;/p&gt;

&lt;p&gt;To take on this task one of the most obvious routes was to use something like OpenClaw. Except, OpenClaw is too new, and a little too much for what I needed, so like any good engineer I decided to roll my own. &lt;em&gt;&lt;a href="https://openclaw.ai/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; is an open-source, autonomous AI agent that runs locally on a your machine or VPS to automate tasks&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Recently &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore&lt;/a&gt; released its managed harness in preview. You define the agent in a config file and a system prompt, and then deploy it. That was it! From my research it looked very straightforward to get up and running, and it could save me a lot of time in the long run. So I decided to take the journey to create my very own Jarvis.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want the conceptual breakdown of what a harness actually is in more detail, &lt;a href="https://dev.to/aws/what-is-an-agent-harness-a-hands-on-guide-with-agentcore-harness-1h33"&gt;my friend Morgan's post on dev.to&lt;/a&gt; covers that well. This post is the hands-on build.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After several hours of tinkering and research I created "The Pulse." It opens &lt;a href="https://news.ycombinator.com" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt; in a real browser, grabs Reddit posts via RSS, saves everything to persistent storage, and every 6 hours writes a trending stories digest. It messages me every day, or I can message it anytime and ask what's happening in AI. It reads from the data it's been collecting all day.&lt;/p&gt;

&lt;p&gt;If you're more like me and love videos, check out the full video below on how to get started. I'm also including the source code so you can try it yourself. Otherwise continue on and learn all about how to get started! Feel free to let me know how it goes.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/sCRPT6dRE4k"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full code:&lt;/strong&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/aws-samples" rel="noopener noreferrer"&gt;
        aws-samples
      &lt;/a&gt; / &lt;a href="https://github.com/aws-samples/sample-AgentCore-Managed-Harness-News" rel="noopener noreferrer"&gt;
        sample-AgentCore-Managed-Harness-News
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;AgentCore Managed Harness News Sample — The Pulse&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A sample AgentCore managed harness project that runs a scheduled news digest agent ("The Pulse") on Amazon Bedrock AgentCore and posts the results to Telegram.&lt;/p&gt;
&lt;p&gt;The agent is scheduled with EventBridge Scheduler, invoked via a Lambda trigger, and uses AgentCore Memory to retain context across runs. It's a realistic end-to-end example of building a recurring, long-running agent on AWS.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What's in here&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;AgentCore-News-Sample/
├── agentcore/
│   ├── agentcore.json          # Project config (runtimes, memories, etc.)
│   ├── aws-targets.json        # AWS account + region to deploy into
│   ├── cdk/                    # CDK infrastructure (@aws/agentcore-cdk)
│   │   ├── bin/cdk.ts          # CDK entry — synthesizes both stacks
│   │   ├── lib/cdk-stack.ts    # AgentCore stack (agent, memory, roles)
│   │   ├── lib/scheduler-stack.ts  # Scheduler + Lambda trigger
│   │   └── lambda/pulse-trigger/   # Lambda that invokes the harness
│   └── .llm-context/           # Type definitions for AI coding assistants&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aws-samples/sample-AgentCore-Managed-Harness-News" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;AgentCore Harness is in preview. The CLI, pricing, and some APIs may change. Check the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore docs&lt;/a&gt; for the latest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt;
  Table of Contents
  &lt;ul&gt;
&lt;li&gt;What We're Building&lt;/li&gt;
&lt;li&gt;Why Not Just Use Lambda?&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Create the Harness Project&lt;/li&gt;
&lt;li&gt;Configure the Agent&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;li&gt;Test the Hourly Collection&lt;/li&gt;
&lt;li&gt;Test the Summary Digest&lt;/li&gt;
&lt;li&gt;Ask It Questions&lt;/li&gt;
&lt;li&gt;Pull Data Out Without Burning Tokens&lt;/li&gt;
&lt;li&gt;Schedule It With EventBridge and Lambda&lt;/li&gt;
&lt;li&gt;Troubleshooting&lt;/li&gt;
&lt;li&gt;Cost Estimate&lt;/li&gt;
&lt;li&gt;Cleanup&lt;/li&gt;
&lt;li&gt;What's Next&lt;/li&gt;
&lt;/ul&gt;



&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpan3tm1n4vjbpmz3437.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpan3tm1n4vjbpmz3437.png" alt="The Pulse architecture diagram" width="800" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't worry if this diagram looks intimidating! The most complicated part is just getting the EventBridge connected with the correct IAM roles to the harness and dealing with the infrastructure code if you're new to that. Let's break down the flow here.&lt;/p&gt;

&lt;p&gt;EventBridge fires every hour and triggers a Lambda. &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/what-is-amazon-eventbridge.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;EventBridge&lt;/a&gt; is AWS's scheduler service, think of it like a cron job in the cloud. The &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/welcome.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Lambda&lt;/a&gt; makes one API call to the harness. Inside the harness, the agent runs in its own microVM, which is basically a lightweight virtual machine that spins up just for your session with its own CPU, memory, and filesystem. The agent opens a browser, scrapes HN, fetches Reddit RSS, and saves a snapshot. Every 6th run it reads all the snapshots, finds trends, and writes a digest. And because the session ID stays the same across runs, all that data accumulates on &lt;code&gt;/mnt/data&lt;/code&gt;. You can also just talk to it anytime through the CLI and ask what's trending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Use Lambda?
&lt;/h2&gt;

&lt;p&gt;Before we get into the details, one question you may be thinking is couldn't we do all this with just a Lambda, some &lt;a href="https://github.com/strands-agents/sdk-python" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt; code, an LLM, and a dream? For a simple one-shot task, you are probably right. But this agent needs a few things Lambda can't give you, which makes this approach well worth it.&lt;/p&gt;

&lt;p&gt;First, the agent opens a real browser to scrape Hacker News. Lambda can't spin up a managed browser session. You'd need &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Fargate&lt;/a&gt; plus Playwright, and now you're managing containers. I love containers, but let's skip it for this scenario.&lt;/p&gt;

&lt;p&gt;Second, it saves hourly snapshots to &lt;code&gt;/mnt/data/runs/&lt;/code&gt; and reads them back 6 hours later. Lambda's &lt;code&gt;/tmp&lt;/code&gt; gets wiped after every invocation. You'd need &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;S3&lt;/a&gt; plus custom read/write logic for every file operation. That's a lot of work for what should be "save a file."&lt;/p&gt;

&lt;p&gt;Third, the 6-hour summary run reads all the accumulated data, compares scores across snapshots, identifies trends, and writes a digest. That can take a while. Lambda maxes out at 15 minutes. The harness defaults to a 1-hour timeout per invocation (I set mine to 30 minutes since that's plenty), and the microVM itself can stay alive for up to 8 hours across multiple invocations.&lt;/p&gt;

&lt;p&gt;And then there's memory. AgentCore Memory is a built-in service that stores facts, preferences, and conversation summaries across sessions. So "What did I ask about yesterday?" works because the memory carries context forward. In Lambda, you'd need &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;DynamoDB&lt;/a&gt; plus a custom integration.&lt;/p&gt;

&lt;p&gt;Could you build all of this with Lambda plus S3 plus Fargate plus DynamoDB plus Step Functions? Sure. That's five services instead of one. That's four too many for this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 20+&lt;/li&gt;
&lt;li&gt;AWS credentials configured in a preview region (&lt;code&gt;us-west-2&lt;/code&gt;, &lt;code&gt;us-east-1&lt;/code&gt;, &lt;code&gt;eu-central-1&lt;/code&gt;, or &lt;code&gt;ap-southeast-2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CDK bootstrapped in your account (&lt;code&gt;cdk bootstrap&lt;/code&gt;). &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/home.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;CDK&lt;/a&gt; is the AWS Cloud Development Kit. It lets you define infrastructure in TypeScript instead of clicking through the console. If you haven't used it before, &lt;code&gt;npm install -g aws-cdk&lt;/code&gt; and then &lt;code&gt;cdk bootstrap&lt;/code&gt; in your account.&lt;/li&gt;
&lt;li&gt;The AgentCore CLI:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @aws/agentcore@preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Bedrock model access for Claude Sonnet 4.6 in your region. As of late 2025, Bedrock gives you automatic access to all serverless models in your region without needing to enable them individually. If your organization uses IAM policies or SCPs to restrict model access, make sure Sonnet isn't blocked. You can verify by opening the &lt;a href="https://us-west-2.console.aws.amazon.com/bedrock/home?region=us-west-2&amp;amp;trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Bedrock console&lt;/a&gt; and trying the model in the playground.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create the Harness Project
&lt;/h2&gt;

&lt;p&gt;This is where the fun begins. We'll create our very first managed harness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model-provider&lt;/span&gt; bedrock &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-storage-mount-path&lt;/span&gt; /mnt/data

&lt;span class="nb"&gt;cd &lt;/span&gt;thepulse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one command scaffolds the whole project. Harness config, memory, session storage, CDK app, IAM role. The &lt;code&gt;--session-storage-mount-path&lt;/code&gt; flag wires up persistent storage so the platform creates a &lt;code&gt;/mnt/data&lt;/code&gt; mount point for you. You don't have to worry about a Dockerfile, or container to manage.&lt;/p&gt;

&lt;p&gt;Now add the browser tool. AgentCore Browser is a managed headless Chrome instance that the harness can spin up on demand. Your agent can navigate pages, click elements, extract content, basically anything you'd do with Playwright but without managing the browser infrastructure yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore add tool &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="nt"&gt;--type&lt;/span&gt; agentcore_browser &lt;span class="nt"&gt;--name&lt;/span&gt; browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what you end up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;thepulse/
├── agentcore/
│   ├── agentcore.json          # Project-level resource config
│   ├── aws-targets.json        # Deployment targets (account, region)
│   ├── .env.local              # Local dev config (gitignored)
│   └── cdk/                    # CDK TypeScript app
├── app/
│   └── thepulse/
│       ├── harness.json        # Agent config
│       └── system-prompt.md    # Agent instructions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure the Agent
&lt;/h2&gt;

&lt;p&gt;Our AgentCore CLI already set up the model, memory, and session storage. You just need to tweak the execution limits and write the system prompt.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;app/thepulse/harness.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"thepulse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bedrock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"modelId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"global.anthropic.claude-sonnet-4-6"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agentcore_browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"browser"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"thepulseMemory"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sessionStoragePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mnt/data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxIterations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeoutSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth mentioning here.&lt;/p&gt;

&lt;p&gt;I set &lt;code&gt;maxIterations&lt;/code&gt; to 75 because browser interactions are chatty. That eats through iterations fast, and 75 gives room for the full HN browser scrape plus Reddit RSS plus file operations without the agent hitting the ceiling.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;timeoutSeconds&lt;/code&gt; is 1800 (30 minutes). Browser sessions take time, and the summary run reads a lot of data. I didn't hit too many timeouts when testing, but this was a just-in-case update.&lt;/p&gt;

&lt;p&gt;This next setting is really useful. The platform creates the &lt;code&gt;/mnt/data&lt;/code&gt; mount point automatically when you set &lt;code&gt;sessionStoragePath&lt;/code&gt;. This mount point will be shared per session, no matter if the session times out.&lt;/p&gt;

&lt;p&gt;Now edit &lt;code&gt;app/thepulse/system-prompt.md&lt;/code&gt;. The CLI created this file with a default prompt. Replace it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are The Pulse, an AI research agent that monitors Hacker News
and Reddit for AI/ML stories.

You have two modes:

&lt;span class="gu"&gt;### HOURLY COLLECTION&lt;/span&gt;
When the prompt says "hourly run":

&lt;span class="gs"&gt;**Hacker News (browser):**&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Use the browser tool to open https://news.ycombinator.com
&lt;span class="p"&gt;2.&lt;/span&gt; Extract the top 30 stories: title, score, comment count, URL
&lt;span class="p"&gt;3.&lt;/span&gt; Close the browser session when done

&lt;span class="gs"&gt;**Reddit (RSS — do NOT use the browser):**&lt;/span&gt;
Reddit blocks automated browser access (403). Use the RSS feed instead:
&lt;span class="p"&gt;1.&lt;/span&gt; Shell: &lt;span class="sb"&gt;`curl -s -A 'ThePulse/1.0'
   'https://www.reddit.com/r/MachineLearning/.rss'`&lt;/span&gt;
&lt;span class="p"&gt;2.&lt;/span&gt; Parse the XML to extract post titles, URLs, authors, and dates
&lt;span class="p"&gt;3.&lt;/span&gt; Note: RSS does not include upvote scores. That's expected.
&lt;span class="p"&gt;4.&lt;/span&gt; Also try r/artificial: &lt;span class="sb"&gt;`curl -s -A 'ThePulse/1.0'
   'https://www.reddit.com/r/artificial/.rss'`&lt;/span&gt;

&lt;span class="gs"&gt;**Save the snapshot:**&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Combine HN and Reddit data into a single JSON file
&lt;span class="p"&gt;2.&lt;/span&gt; Save to /mnt/data/runs/YYYY-MM-DD-HH.json
&lt;span class="p"&gt;3.&lt;/span&gt; If a previous hour's snapshot exists (check /mnt/data/runs/),
   compare and flag:
&lt;span class="p"&gt;   -&lt;/span&gt; Stories that appeared in both snapshots (persistent)
&lt;span class="p"&gt;   -&lt;/span&gt; HN stories with rising scores

&lt;span class="gu"&gt;### SUMMARY DIGEST&lt;/span&gt;
When the prompt says "generate summary":
&lt;span class="p"&gt;1.&lt;/span&gt; Read all snapshots from /mnt/data/runs/ for the last 6 hours
&lt;span class="p"&gt;2.&lt;/span&gt; Identify stories that appeared in multiple snapshots
   (persistent presence = trending)
&lt;span class="p"&gt;3.&lt;/span&gt; Identify stories with rising scores across snapshots
&lt;span class="p"&gt;4.&lt;/span&gt; Deduplicate and rank by persistence, score growth, and
   comment activity
&lt;span class="p"&gt;5.&lt;/span&gt; Write a markdown digest to
   /mnt/data/digests/YYYY-MM-DD-{morning|afternoon|evening|night}.md
&lt;span class="p"&gt;6.&lt;/span&gt; Include the top 10 trending stories with: title, source
   (HN/Reddit), peak score, trend direction, URL

&lt;span class="gu"&gt;### INTERACTIVE&lt;/span&gt;
When a user asks a question:
&lt;span class="p"&gt;-&lt;/span&gt; Read from /mnt/data/runs/ and /mnt/data/digests/ to answer
&lt;span class="p"&gt;-&lt;/span&gt; Use memory for conversational context

&lt;span class="gu"&gt;## Important&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; /mnt/data is persistent session storage. Use the same session ID
  across runs so data accumulates.
&lt;span class="p"&gt;-&lt;/span&gt; Always create directories before writing:
  &lt;span class="sb"&gt;`mkdir -p /mnt/data/runs /mnt/data/digests`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I put the collection to be hourly and the snapshots to run every six hours. Retrospectively, this is a lot of data, and I could certainly have expanded that. I'd say collecting twice a day and having a snapshot created once a day would be fine as well. Feel free to tweak this prompt to your liking.&lt;/p&gt;

&lt;p&gt;One gotcha I'd like to pass on is that Reddit doesn't like agents scraping its data. The agent kept trying to open Reddit in the browser, getting 403'd, and burning 5-6 iterations before giving up. I sat there watching it retry over and over thinking "please just stop." The RSS feed at &lt;code&gt;reddit.com/r/MachineLearning/.rss&lt;/code&gt; works every time. Putting it explicitly in the system prompt saves you from that pain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command. It provisions the harness, memory, IAM role, session storage, and the underlying microVM infrastructure. This takes about 3-5 minutes because it's also spinning up AgentCore Memory and running CDK for the IAM roles.&lt;/p&gt;

&lt;p&gt;You can verify what got created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore status &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test the Hourly Collection
&lt;/h2&gt;

&lt;p&gt;To test this out you'll need a stable session ID so &lt;code&gt;/mnt/data&lt;/code&gt; accumulates across runs, as I mentioned earlier. The session ID needs to be at least 33 characters (a UUID is 36, so that works). This is important because the same session ID means the same filesystem. That's how data builds up over time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SESSION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"thepulse-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uuidgen&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;  &lt;span class="c"&gt;# Save this. You'll reuse it for every run.&lt;/span&gt;

agentcore invoke &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--actor-id&lt;/span&gt; scheduler &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Hourly run. Collect AI/ML stories from Hacker News and Reddit. Save the snapshot."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent will open a managed Chrome browser, navigate to &lt;code&gt;news.ycombinator.com&lt;/code&gt;, extract stories, fetch Reddit via RSS, and save everything to &lt;code&gt;/mnt/data/runs/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Verify it worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore invoke &lt;span class="nt"&gt;--exec&lt;/span&gt; &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"ls -la /mnt/data/runs/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;--exec&lt;/code&gt; flag is one of my favorite things about the harness. It runs a shell command directly on the microVM. It's not connecting to any LLM so no tokens are used. You can use it to poke around the filesystem anytime without spending money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the Summary Digest
&lt;/h2&gt;

&lt;p&gt;Run the collection a couple times (or wait a few hours), then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore invoke &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--actor-id&lt;/span&gt; scheduler &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Generate summary. Read all snapshots from the last 6 hours and write a trending stories digest."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent reads all the hourly snapshots, identifies stories that appeared multiple times (trending), flags rising scores, and writes a markdown digest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask It Questions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore invoke &lt;span class="nt"&gt;--harness&lt;/span&gt; thepulse &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--actor-id&lt;/span&gt; demo-user &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"What's trending in AI right now?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent reads from &lt;code&gt;/mnt/data/runs/&lt;/code&gt; and &lt;code&gt;/mnt/data/digests/&lt;/code&gt; to answer with real data. Not hallucinated summaries, actual scores and URLs from the snapshots it collected. I really liked this part. The agent isn't guessing. It's reading files it wrote earlier that day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schedule It With EventBridge and Lambda
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;agentcore create&lt;/code&gt; command already scaffolded a CDK app at &lt;code&gt;agentcore/cdk/&lt;/code&gt;. We'll add a Lambda trigger and an EventBridge schedule to that existing project. EventBridge Scheduler is the AWS service that fires events on a cron schedule. We'll have it invoke a Lambda every hour, and that Lambda calls the harness. One &lt;code&gt;agentcore deploy&lt;/code&gt; handles the harness, one &lt;code&gt;cdk deploy&lt;/code&gt; handles the scheduler. I like keeping these separate because you iterate on the agent config way more often than the scheduling infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Up the Lambda
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; agentcore/cdk/lambda/pulse-trigger
&lt;span class="nb"&gt;cd &lt;/span&gt;agentcore/cdk/lambda/pulse-trigger
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @aws-sdk/client-bedrock-agentcore @aws-sdk/client-secrets-manager
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript @types/node esbuild
&lt;span class="nb"&gt;cd&lt;/span&gt; ../../../..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;agentcore/cdk/lambda/pulse-trigger/index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BedrockAgentCoreClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InvokeHarnessCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-bedrock-agentcore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;SecretsManagerClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;GetSecretValueCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-secrets-manager&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TelegramSecret&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;defaultChatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agentCore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BedrockAgentCoreClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secrets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SecretsManagerClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_REGION&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Cache the secret across warm invocations&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cachedSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TelegramSecret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SECRET_TTL_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 5 minutes&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TelegramSecret&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedSecret&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cachedSecret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cachedSecret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_SECRET_ARN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;secretArn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing TELEGRAM_SECRET_ARN env var&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetSecretValueCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;SecretId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;secretArn&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TelegramSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecretString&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;cachedSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;SECRET_TTL_MS&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendToTelegram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Telegram's message limit is 4096 chars. Split if needed.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\s\S]{1,4000}&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://api.telegram.org/bot&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sendMessage`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;parse_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Markdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Telegram send failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getUTCHours&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STABLE_SESSION_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;harnessArn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HARNESS_ARN&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSummaryRun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isSummaryRun&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generate summary. Read all snapshots from the last 6 hours and write a trending stories digest.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hourly run. Collect AI/ML stories from Hacker News and Reddit. Save the snapshot.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InvokeHarnessCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;harnessArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;runtimeSessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;actorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scheduler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agentCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Collect the full response text from the stream&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;responseText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;chunkCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;chunkCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contentBlockDelta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;contentBlockDelta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;responseText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`\n[pulse-trigger] Stream complete — &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chunkCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; chunks`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// On summary runs, send the digest to Telegram&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isSummaryRun&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultChatId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendToTelegram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultChatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isSummaryRun&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;collection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;digestSent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isSummaryRun&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what's happening here.&lt;/p&gt;

&lt;p&gt;The Lambda passes a stable session ID (from an environment variable) so every hourly run writes to the same &lt;code&gt;/mnt/data&lt;/code&gt;. It collects the full response text from the stream and logs it to &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;CloudWatch&lt;/a&gt;. On summary runs, it sends the digest to Telegram using a bot token stored in &lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Secrets Manager&lt;/a&gt;. Secrets Manager is the AWS service for storing sensitive values like API keys and tokens. You don't want a bot token sitting in an environment variable where anyone with console access can see it. The &lt;code&gt;sendToTelegram&lt;/code&gt; function handles Telegram's 4096 character limit by chunking the text. I found the CloudWatch logging really helpful for debugging early on when the agent would occasionally get stuck in a browser loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add the Scheduler Stack
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;agentcore/cdk/lib/scheduler-stack.ts&lt;/code&gt;. This is a CDK stack, which is basically a unit of deployment. Everything in this file gets deployed together as one &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;CloudFormation&lt;/a&gt; stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TimeZone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NodejsFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-lambda-nodejs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Secret&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-secretsmanager&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-iam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-scheduler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-scheduler-targets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;constructs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PulseSchedulerStackProps&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;agentRuntimeArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;harnessArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;stableSessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;telegramSecretArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PulseSchedulerStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PulseSchedulerStackProps&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;telegramSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromSecretCompleteArn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TelegramSecret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegramSecretArn&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;triggerFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NodejsFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PulseTrigger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_22_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../lambda/pulse-trigger/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;HARNESS_ARN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;harnessArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;STABLE_SESSION_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stableSessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;TELEGRAM_SECRET_ARN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telegramSecretArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;bundling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;externalModules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;triggerFn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToRolePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bedrock-agentcore:InvokeHarness&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bedrock-agentcore:InvokeAgentRuntime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;harnessArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentRuntimeArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Read access to the Telegram bot token secret&lt;/span&gt;
    &lt;span class="nx"&gt;telegramSecret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;triggerFn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HourlySchedule&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ScheduleExpression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TimeZone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;triggerFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The Pulse — hourly AI/ML news collection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TriggerFunctionName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;triggerFn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;telegramSecret.grantRead(triggerFn)&lt;/code&gt; line gives the Lambda permission to read the bot token from Secrets Manager. The bot token lives there instead of in environment variables so it doesn't show up in CloudFormation outputs or the Lambda console.&lt;/p&gt;

&lt;p&gt;Wire it into &lt;code&gt;agentcore/cdk/bin/cdk.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PulseSchedulerStack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../lib/scheduler-stack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ... existing AgentCore stack code ...&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PulseSchedulerStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ThePulseScheduler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;harnessArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_WITH_YOUR_HARNESS_ARN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;agentRuntimeArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_WITH_YOUR_RUNTIME_ARN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stableSessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_WITH_YOUR_SESSION_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;telegramSecretArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACE_WITH_YOUR_SECRET_ARN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm showing placeholders here to make it clear what goes where. You can get your harness ARN and runtime ARN with &lt;code&gt;agentcore status --json&lt;/code&gt;, and use the same session ID you've been testing with. The Telegram secret ARN comes from Secrets Manager (store your bot token and chat ID as a JSON object with &lt;code&gt;botToken&lt;/code&gt; and &lt;code&gt;defaultChatId&lt;/code&gt; keys).&lt;/p&gt;

&lt;p&gt;That said, you shouldn't hardcode ARNs and secrets into source files you commit to git. The &lt;a href="https://github.com/aws-samples/sample-AgentCore-Managed-Harness-News" rel="noopener noreferrer"&gt;actual repo&lt;/a&gt; resolves the harness ARN and runtime ARN automatically from the deployed state file that &lt;code&gt;agentcore deploy&lt;/code&gt; creates, and reads the Telegram secret ARN and session ID from environment variables. That way nothing sensitive ends up in version control. Check the &lt;code&gt;cdk.ts&lt;/code&gt; in the repo for the full implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy and Test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install esbuild in the CDK project (needed for NodejsFunction)&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;agentcore/cdk &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install &lt;/span&gt;esbuild &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ../..

&lt;span class="c"&gt;# Deploy the harness&lt;/span&gt;
agentcore deploy

&lt;span class="c"&gt;# Deploy the scheduler stack&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;agentcore/cdk &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx cdk deploy ThePulseScheduler &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ../..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the Lambda directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--function-name&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;aws cloudformation describe-stacks &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--stack-name&lt;/span&gt; ThePulseScheduler &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Stacks[0].Outputs[?OutputKey==`TriggerFunctionName`].OutputValue'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-west-2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cli-read-timeout&lt;/span&gt; 900 &lt;span class="se"&gt;\&lt;/span&gt;
  /tmp/lambda-response.json

&lt;span class="nb"&gt;cat&lt;/span&gt; /tmp/lambda-response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--cli-read-timeout 900&lt;/code&gt; is important. The harness invocation takes a few minutes (the agent is browsing HN, fetching RSS, saving data), and the AWS CLI's default timeout is 60 seconds. Without it, you'll get a &lt;code&gt;Read timeout&lt;/code&gt; error even though the Lambda is still running fine. Don't make this mistake like I did.&lt;/p&gt;

&lt;p&gt;You should see &lt;code&gt;{"statusCode":200,"hour":...,"type":"collection"}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;A few things I hit while building this. Hopefully they save you some time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;npm ci&lt;/code&gt; fails during CDK synth.&lt;/strong&gt; If you use &lt;code&gt;nodeModules&lt;/code&gt; in the bundling config instead of &lt;code&gt;externalModules: []&lt;/code&gt;, CDK copies your &lt;code&gt;package-lock.json&lt;/code&gt; into a temp directory and runs &lt;code&gt;npm ci&lt;/code&gt;. If the lock file is even slightly out of sync with &lt;code&gt;package.json&lt;/code&gt;, it fails. I went back and forth on this for a while before landing on the &lt;code&gt;externalModules: []&lt;/code&gt; approach that bundles everything with esbuild and skips &lt;code&gt;npm ci&lt;/code&gt; entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;AccessDeniedException&lt;/code&gt; on &lt;code&gt;ConverseStream&lt;/code&gt;.&lt;/strong&gt; This one got me. Bedrock now gives automatic access to all models in your region, so the old "Model Access" page is gone. But if your organization uses IAM (Identity and Access Management) policies or SCPs (Service Control Policies) to restrict which models can be invoked, you'll get this error. Check with your admin to make sure the model isn't blocked by a deny policy. I hit this when I tried to use Haiku for cheaper collection runs in an account that had an SCP limiting models to Sonnet only. The error message doesn't make it obvious that it's a permissions issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda times out but the agent is still running.&lt;/strong&gt; The Lambda timeout (15 min max) and the harness timeout (30 min) are separate. If the agent takes longer than the Lambda allows, the Lambda dies but the harness session keeps running in the background. Check CloudWatch logs for the Lambda, and use &lt;code&gt;agentcore logs --harness thepulse --since 1h&lt;/code&gt; for the harness side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reddit returns 403 in the browser.&lt;/strong&gt; Reddit blocks automated browser access. The system prompt tells the agent to use RSS instead, but if you're iterating on the prompt and forget to include that instruction, the agent will waste iterations trying to open Reddit in the browser. Ask me how I know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Estimate
&lt;/h2&gt;

&lt;p&gt;The main cost here is Bedrock model usage. Each hourly collection run uses around 10-15K tokens (browser interactions are chatty), and summary runs use more since they read all the accumulated data. Lambda and EventBridge are basically free at this scale.&lt;/p&gt;

&lt;p&gt;You can cut costs by enabling a cheaper model like Haiku in the Bedrock console and passing a model override for collection runs. Keep Sonnet for the summary runs that need more reasoning. The harness supports per-invocation model overrides so you don't need to redeploy. You can also just reduce the number of times it collects data.&lt;/p&gt;

&lt;p&gt;AgentCore Runtime and Browser have no separate charge during preview.&lt;/p&gt;

&lt;p&gt;Check the &lt;a href="https://aws.amazon.com/bedrock/pricing/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Bedrock pricing page&lt;/a&gt; for current model token costs and the &lt;a href="https://aws.amazon.com/bedrock/agentcore/pricing/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore pricing page&lt;/a&gt; for runtime and browser session rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;Make sure you always clean up your stacks after trying out tutorials!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Delete the scheduler stack&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;agentcore/cdk &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx cdk destroy ThePulseScheduler &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ../..

&lt;span class="c"&gt;# Delete the harness and all AgentCore resources&lt;/span&gt;
agentcore destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;agentcore destroy&lt;/code&gt; command removes the harness, memory, IAM role, and session storage. The CDK destroy removes the Lambda, EventBridge schedule, and any IAM roles CDK created.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This is a starting point. I have thoughts on a few directions I could take my very own Jarvis 😊.&lt;/p&gt;

&lt;p&gt;Right now the Telegram integration is outbound only. I want to add two-way chat so I can message the bot and ask questions (without just using the command line). That means an &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt; webhook and a second Lambda that invokes the harness in interactive mode. The agent reads from &lt;code&gt;/mnt/data&lt;/code&gt; to answer with real data.&lt;/p&gt;

&lt;p&gt;More sources would be easy to add. The system prompt is just markdown. Add instructions for other news sources that a browser can reach. The nice thing is it would just take a prompt update and a new run of &lt;code&gt;agentcore deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I also want to try switching models per run. Enable Haiku in the Bedrock console and pass &lt;code&gt;--model-id&lt;/code&gt; on collection runs for cheaper hourly scrapes. Keep Sonnet for the summary runs that need more reasoning. The harness supports this per-invocation without a redeploy.&lt;/p&gt;

&lt;p&gt;And eventually, a dashboard. Have the agent generate an HTML page from the accumulated data and sync it to S3. Use &lt;code&gt;--exec&lt;/code&gt; to run &lt;code&gt;aws s3 sync&lt;/code&gt; from the microVM.&lt;/p&gt;

&lt;p&gt;After building this, I'm convinced the harness approach works really well for simpler use cases like my personal news Jarvis. If your agent needs persistent state, scheduled runs, and a browser, this is way less infrastructure than stitching it together yourself. If you just need a one-shot agent that answers a question, stick with Lambda. And of course if you need maximum flexibility, using AgentCore runtime is your best bet!&lt;/p&gt;

&lt;p&gt;Let me know in the comments what you think! Thanks!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore Harness docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-tool.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore Browser docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-tools.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Connect to tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/harness-memory.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Persist memory and filesystem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws/agentcore-cli" rel="noopener noreferrer"&gt;AgentCore CLI @preview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/bedrock/agentcore/pricing/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AgentCore pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>agents</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Stop Reaching for Python: Strands Agents TypeScript SDK Just Hit 1.0</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Mon, 04 May 2026 20:11:08 +0000</pubDate>
      <link>https://dev.to/aws/stop-reaching-for-python-strands-agents-typescript-sdk-just-hit-10-4lk6</link>
      <guid>https://dev.to/aws/stop-reaching-for-python-strands-agents-typescript-sdk-just-hit-10-4lk6</guid>
      <description>&lt;p&gt;A lot of production codebases are TypeScript. A lot of agent frameworks are Python. If yours is both, you either rewrite your stack or build a bridge between two languages. Strands Agents just shipped 1.0 of the TypeScript SDK, so now you don't have to. It's the full framework, native TypeScript types and all. And it does things Python can't, like running agents in the browser.&lt;/p&gt;

&lt;p&gt;The Python SDK has been in production since May 2025. This is the same model-driven approach, now with full TypeScript types and Zod-validated tools.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure, I'm a Developer Advocate for AWS. Strands is an open source project from AWS. I've been building with the Python SDK for months, so I was curious how the TypeScript version compares. So far, it's been great.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this post we'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Getting a basic agent running&lt;/li&gt;
&lt;li&gt;Defining type-safe tools with Zod&lt;/li&gt;
&lt;li&gt;Connecting MCP servers&lt;/li&gt;
&lt;li&gt;Streaming responses&lt;/li&gt;
&lt;li&gt;Running agents in the browser&lt;/li&gt;
&lt;li&gt;Multi-agent patterns: agent-as-tool, Graph, and Swarm&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Install the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @strands-agents/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Lines to a Working Agent
&lt;/h2&gt;

&lt;p&gt;The API is small on purpose. Create an agent, then invoke.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a helpful assistant.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What makes TypeScript great for building agents?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bedrock is the default model provider. If you want OpenAI, Anthropic, Google, or anything that works with the Vercel AI SDK, you swap one import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OpenAIModel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk/models/openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAIModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-5.4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a helpful assistant.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just swap the model import. The agent, tools, and invocation pattern don't change at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zod Tools
&lt;/h2&gt;

&lt;p&gt;TypeScript is a good fit here because you're defining the contract between your agent and your tools at the type level. You define a tool with a Zod schema and get runtime validation plus full type inference at compile time. The model can't pass garbage to your tool without Zod catching it, and your editor knows the exact shape of every input before you even run anything.&lt;/p&gt;

&lt;p&gt;Here's a GitHub lookup tool in about 30 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;githubRepo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_github_repo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Get info about a GitHub repository.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Repository owner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Repository name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.github.com`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;`/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; — ⭐ &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stargazers_count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; stars`&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;githubRepo&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a developer assistant.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;input&lt;/code&gt; parameter in that callback is fully typed. Your editor knows &lt;code&gt;input.owner&lt;/code&gt; is a string. No &lt;code&gt;any&lt;/code&gt;, no type casting.&lt;/p&gt;

&lt;p&gt;The SDK also ships with built-in tools for bash, file editing, HTTP requests, and notebooks. Your agent can read files, hit APIs, and run shell commands without writing any tool code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk/vended-tools/bash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileEditor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk/vended-tools/file-editor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;httpRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk/vended-tools/http-request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;notebook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk/vended-tools/notebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;bash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;httpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notebook&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a helpful coding assistant.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  MCP
&lt;/h2&gt;

&lt;p&gt;If you're already using MCP servers, they plug right in. I tested this with the filesystem MCP server and it worked well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;McpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StdioClientTransport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/sdk/client/stdio.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StdioClientTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;npx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/server-filesystem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any MCP-compatible server works the same way. One &lt;code&gt;McpClient&lt;/code&gt; import and you're done. I've used this with the filesystem server, a Postgres server, and a few custom ones, and the setup is identical every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming
&lt;/h2&gt;

&lt;p&gt;Async iterators let you generate chunks and stream them back to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a creative storyteller.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tell me a short story about a brave toaster.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modelStreamUpdateEvent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modelContentBlockDeltaEvent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textDelta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event type checking is verbose. You're narrowing through three nested types to get to the actual text delta. That's just how TypeScript streaming works when the event union is this wide. Once you have the pattern, you copy it.&lt;/p&gt;

&lt;h2&gt;
  
  
  It Runs in the Browser
&lt;/h2&gt;

&lt;p&gt;You can run agents in the browser. Most people don't think about this because the Python SDK can't do it natively.&lt;/p&gt;

&lt;p&gt;There's a demo in the &lt;a href="https://github.com/strands-agents/sdk-typescript/tree/main/strands-ts/examples/browser-agent" rel="noopener noreferrer"&gt;&lt;code&gt;strands-agents/sdk-typescript&lt;/code&gt;&lt;/a&gt; repo where you chat with an agent and it builds a live HTML canvas in real time. The agent runs entirely client-side.&lt;/p&gt;

&lt;p&gt;That opens up things that weren't possible before: local-first tools where the agent runs on the user's machine, interactive assistants embedded in your app without a server round-trip, or even running the model locally if you want to skip the round-trip entirely.&lt;/p&gt;

&lt;p&gt;I cloned the demo and had it running in a few minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/strands-agents/sdk-typescript.git
&lt;span class="nb"&gt;cd &lt;/span&gt;sdk-typescript/strands-ts/examples/browser-agent
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Multi-Agent Patterns
&lt;/h2&gt;

&lt;p&gt;The SDK ships with three ways to combine agents: agent-as-tool, Graph, and Swarm. Each one fits a different problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent-as-tool
&lt;/h3&gt;

&lt;p&gt;This is the one you'll reach for most. Pass a sub-agent directly into another agent's &lt;code&gt;tools&lt;/code&gt; array and it gets wrapped automatically. The outer agent decides when to call it, just like any other tool.&lt;/p&gt;

&lt;p&gt;Think of a writer agent that has a researcher agent as a tool. The writer decides when it needs facts, calls the researcher, gets the results back, and uses them to write. The code below is exactly that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;researcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;researcher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Researcher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finds factual information and returns concise bullet-point findings.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a fact-finder. Return 3-5 concise bullet points. Be brief.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a prose writer. Use the Researcher tool to gather facts, then write one short paragraph.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;researcher&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// sub-agent auto-wrapped via asTool()&lt;/span&gt;
  &lt;span class="na"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Write a paragraph about the deepest point in the ocean.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Graph
&lt;/h3&gt;

&lt;p&gt;Graph is for deterministic pipelines. You define agents as nodes and wire them together with edges. A node runs once all its upstream dependencies complete, so execution order is guaranteed. Nodes with no dependency between them can run in parallel.&lt;/p&gt;

&lt;p&gt;This pattern works well when you know the steps upfront and need them to always run in the same order. A research pipeline is a good example. Plan the questions, research each one, then synthesize the results. You wouldn't want the synthesizer running before the researcher finishes, and you don't want the model deciding to skip steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Graph&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;planner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;planner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Break the request into 2 short research questions.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;researcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;researcher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Answer each research question in 1-2 sentences. Be concise.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;synthesiser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;synthesiser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Write a 2-3 sentence summary of the research.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Graph&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;planner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;researcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;synthesiser&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;planner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;researcher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;researcher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;synthesiser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// MultiAgentResult.content holds the combined output from terminus nodes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What are the main impacts of lithium mining?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;edges&lt;/code&gt; array defines the DAG (directed acyclic graph — basically a dependency map). &lt;code&gt;planner&lt;/code&gt; runs first, feeds into &lt;code&gt;researcher&lt;/code&gt;, which feeds into &lt;code&gt;synthesiser&lt;/code&gt;. The final output comes from the terminus node via &lt;code&gt;result.content&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Swarm
&lt;/h3&gt;

&lt;p&gt;Swarm is for when the path isn't known upfront. You define a set of agents with descriptions, pick a starting agent, and the model decides at runtime which agent handles the next step. Each agent either produces a final answer or hands off to another agent.&lt;/p&gt;

&lt;p&gt;I'd reach for this when building something like a customer support bot where you don't know which specialist is needed until you see what the user actually wrote. The routing logic lives in the model, not in your code. You're not writing &lt;code&gt;if request.includes('billing')&lt;/code&gt; anywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Swarm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@strands-agents/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;triage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;triage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Classifies the user request and routes it to the right specialist.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Route to "billing" or "technical". Do not answer yourself.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;billing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;billing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Handles billing and payment questions.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Resolve billing queries. Do not hand off further.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;technical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;technical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Handles technical and product questions.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Resolve technical queries. Do not hand off further.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;swarm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Swarm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;billing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;technical&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;triage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxSteps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;swarm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My invoice shows the wrong amount.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Heads up if you're on Bedrock:&lt;/strong&gt; Swarm uses &lt;a href="https://strandsagents.com/docs/api/typescript/Swarm/" rel="noopener noreferrer"&gt;structured output&lt;/a&gt; to drive handoffs. It forces a tool call to get the routing decision, and the current Claude Sonnet model on Bedrock throws a "does not support assistant message prefill" error when that happens. Swarm works fine with the Anthropic direct API or other providers that support forced tool choice. If you're on Bedrock and need dynamic routing, agent-as-tool is the safer option for now.&lt;/p&gt;

&lt;p&gt;I haven't tested the plugin system, session persistence, or OpenTelemetry tracing yet. The plugin system looks simple to use and I'll probably dig into it next.&lt;/p&gt;

&lt;p&gt;The SDK is open source. If you build something with it, I want to see it. The browser runtime especially. I'm curious where people take it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Covered
&lt;/h2&gt;

&lt;p&gt;The Strands Agents TypeScript SDK gives you a full agent framework without leaving your existing stack. Here's what we went through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A basic agent runs in two lines. Bedrock is the default, but swapping to OpenAI or Anthropic is one import.&lt;/li&gt;
&lt;li&gt;Zod tools give you runtime validation and compile-time type inference. The model can't pass bad input without Zod catching it.&lt;/li&gt;
&lt;li&gt;MCP servers connect with a single &lt;code&gt;McpClient&lt;/code&gt; import. Any MCP-compatible server works.&lt;/li&gt;
&lt;li&gt;Streaming uses async iterators. Verbose event types, but that's TypeScript being TypeScript.&lt;/li&gt;
&lt;li&gt;The browser runtime is real and working. Clone the demo and see it for yourself.&lt;/li&gt;
&lt;li&gt;For multi-agent work: agent-as-tool for orchestrator/worker setups, Graph for fixed pipelines, Swarm for dynamic routing (just not on Bedrock yet).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to go deeper, the &lt;a href="https://strandsagents.com/docs/user-guide/quickstart/typescript/" rel="noopener noreferrer"&gt;TypeScript quickstart&lt;/a&gt;, the &lt;a href="https://github.com/strands-agents/sdk-typescript" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;, and the &lt;a href="https://strandsagents.com/docs/api/typescript/" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; are the best next stops.&lt;/p&gt;




&lt;h2&gt;
  
  
  Watch the Full Video
&lt;/h2&gt;

&lt;p&gt;If you prefer video format, here's a quick walkthrough of the TypeScript SDK and its key features:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/g5gCMTchjKs"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>ai</category>
      <category>aws</category>
      <category>agents</category>
    </item>
    <item>
      <title>I Tried Vite+ and Replaced My Entire Frontend Toolchain</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Thu, 19 Mar 2026 17:02:19 +0000</pubDate>
      <link>https://dev.to/erikch/i-tried-vite-and-replaced-my-entire-frontend-toolchain-4cgb</link>
      <guid>https://dev.to/erikch/i-tried-vite-and-replaced-my-entire-frontend-toolchain-4cgb</guid>
      <description>&lt;p&gt;If you're a frontend developer in 2026, you've probably had to deal with a lot of different tooling. Package managers, linters, formatters, pre-commit hooks, test runners. The list goes on. And every team has opinions on which ones to use.&lt;/p&gt;

&lt;p&gt;I've worked on apps where hours if not days went into tweaking, upgrading, and swapping out tooling. And the &lt;a href="https://en.wikipedia.org/wiki/Law_of_triviality" rel="noopener noreferrer"&gt;bikeshedding&lt;/a&gt; when someone proposes something new in a meeting? Don't get me started. Earlier in my career I remember spending over an hour debating whether we should add Vitest or keep the old test runner. We ended up not changing anything.&lt;/p&gt;

&lt;p&gt;That's why I really like &lt;a href="https://viteplus.dev/" rel="noopener noreferrer"&gt;Vite+&lt;/a&gt;. Vite+ is a unified toolchain that acts as an entrypoint for web development. It comes out of the team from &lt;a href="https://voidzero.dev/" rel="noopener noreferrer"&gt;VoidZero&lt;/a&gt;, the company founded by Evan You, the creator of Vite and Vue. You can think of it like a ShamWow for your toolchain. One tool that replaces your linter, formatter, test runner, bundler, and package manager all at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I also made a full video walkthrough where I set up a Vue project from scratch, run into the Nuxt issues in real time, and show the formatting and testing in action. If you'd rather watch than read, check it out below!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/2C1HkBrz5Wc"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What's inside Vite+&lt;/li&gt;
&lt;li&gt;A few caveats&lt;/li&gt;
&lt;li&gt;Getting started&lt;/li&gt;
&lt;li&gt;Creating your first app&lt;/li&gt;
&lt;li&gt;
Important commands

&lt;ul&gt;
&lt;li&gt;Checking and linting&lt;/li&gt;
&lt;li&gt;Formatting&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Caching with vp run&lt;/li&gt;
&lt;li&gt;Managing Node.js with vp env&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's inside Vite+
&lt;/h2&gt;

&lt;p&gt;Instead of having to worry about building, installing, adding every tool you need when building an app, and dealing with configurations in 10 different files, you can now rely on one Vite powered toolchain. In essence, Vite+ combines all of these tools into one. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vite.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; as your build tool, now even faster with &lt;a href="https://vite.dev/blog/announcing-vite8" rel="noopener noreferrer"&gt;Vite 8&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vitest.dev/" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt; for testing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://oxc.rs/docs/guide/usage/linter.html" rel="noopener noreferrer"&gt;Oxlint&lt;/a&gt; for linting, replacing ESLint&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://oxc.rs/docs/guide/usage/formatter.html" rel="noopener noreferrer"&gt;Oxfmt&lt;/a&gt; for formatting, replacing Prettier and Biome&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rolldown.rs/" rel="noopener noreferrer"&gt;Rolldown&lt;/a&gt; for bundling, replacing ESBuild&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tsdown.dev/" rel="noopener noreferrer"&gt;tsdown&lt;/a&gt; for library bundling&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; runtime and package manager built in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since many of these tools are written in Rust, the performance gains are real. According to &lt;a href="https://voidzero.dev/posts/announcing-vite-plus-alpha" rel="noopener noreferrer"&gt;VoidZero's announcement&lt;/a&gt;, Oxlint runs 50 to 100x faster than ESLint, Oxfmt formats up to 30x faster than Prettier, and production builds with Vite 8 and Rolldown are 1.6x to 7.7x faster than Vite 7. I had to test this out in my own projects, and using the &lt;code&gt;vp check&lt;/code&gt; command was finishing in under a second on fairly large code bases.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few caveats
&lt;/h2&gt;

&lt;p&gt;While having everything in one tool helps, it does have some DX papercuts at this point. For example, while they do support most package managers (pnpm, npm, yarn), they are missing &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;bun&lt;/a&gt;. There is also some more work to be done with the vite.config.ts on Nuxt and TanStack Start since the tooling for each aren't quite there yet. Nevertheless it's early days in Vite+, and it's still in alpha, so I'm sure the updates are happening soon.&lt;/p&gt;

&lt;p&gt;I'd imagine if you are not sold on the Vite ecosystem, or using toolchains in general, this probably isn't the tool for you. I, like many other developers, have opinions, and often when I create apps I have very bespoke patterns I use. In my case, I use &lt;a href="https://github.com/vuejs/create-vue" rel="noopener noreferrer"&gt;&lt;code&gt;npm create vue@latest&lt;/code&gt;&lt;/a&gt; for every Vue app, and &lt;code&gt;npm create nuxt@latest&lt;/code&gt; for Nuxt apps. The Vue scaffolder does let you opt into ESLint, Prettier, and Vitest during setup, which is nice. But each one gets its own config file, you still have to make sure they play well together, and things like pre-commit hooks are on you to wire up with husky or lint-staged. It adds up. I also have a very specific set of skills and markdown files I use with &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, to help me code.&lt;/p&gt;

&lt;p&gt;With Vite+, all of that comes out of the box. One &lt;code&gt;vp create vue&lt;/code&gt; and I have linting, formatting, testing, and pre-commit hooks ready to go in a single &lt;code&gt;vite.config.ts&lt;/code&gt;. That's what sold me on it.&lt;/p&gt;

&lt;p&gt;Let's take a look at how to set it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;As described in the &lt;a href="https://viteplus.dev/guide/" rel="noopener noreferrer"&gt;Vite+&lt;/a&gt; setup guide you can install it on your macOS/Linux with a simple curl command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://vite.plus | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That should be it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;During the install you'll be asked a few questions around how Vite+ should manage your global Node.js runtime and package manager. You can opt-out of this, and use &lt;code&gt;nvm&lt;/code&gt; or whatever else you'd like. However, I actually think this is really nice feature. With Vite+ you can use the &lt;code&gt;vp env&lt;/code&gt; command to switch between environments really easily. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From here you can either start creating apps, or migrate existing Vite apps over. I've only really tested the creating apps options, so we'll look at that. I have seen a few people using the &lt;code&gt;vp migrate&lt;/code&gt; command to do migration. It seems to work OK, but still requires manual intervention. One cool thing from the &lt;a href="https://voidzero.dev/posts/announcing-vite-plus-alpha" rel="noopener noreferrer"&gt;Vite+ announcement&lt;/a&gt; is that they provide a migration prompt you can paste directly into your AI coding agent. So if you're using &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; or &lt;a href="https://kiro.dev/cli/" rel="noopener noreferrer"&gt;kiro-cli&lt;/a&gt;, you could have it handle the migration for you and just review the changes after. Here's the prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Migration prompt:&lt;/strong&gt; Migrate this project to Vite+. Run &lt;code&gt;vp help&lt;/code&gt; to understand Vite+'s capabilities. Migrations can be run using &lt;code&gt;vp migrate&lt;/code&gt;. Run &lt;code&gt;vp help migrate&lt;/code&gt; for options. After the migration, verify the changes and make sure that type checking, linting, formatting, and tests pass. High-five your human when you are done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creating your first app
&lt;/h2&gt;

&lt;p&gt;After installation you can then create a new app using the &lt;code&gt;vp create&lt;/code&gt; command. This will then ask a few quick questions on what kind of application you'd like and it will then scaffold it all for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmi7didlbl8bw8civzgjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmi7didlbl8bw8civzgjr.png" alt="Image of vp create"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As of this blog post it defaults to a non JS Framework solution. While this is fine for a demo, I don't really get it, because I don't think many people today create websites without some sort of framework or library. Instead you can use this command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp create &amp;lt;template&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right now it supports, &lt;code&gt;vite&lt;/code&gt;, &lt;code&gt;@tanstack/start&lt;/code&gt;, &lt;code&gt;svelte&lt;/code&gt;, &lt;code&gt;next-app&lt;/code&gt;, &lt;code&gt;nuxt&lt;/code&gt;, &lt;code&gt;react-router&lt;/code&gt; and &lt;code&gt;vue&lt;/code&gt;. As I mentioned earlier with &lt;code&gt;Nuxt&lt;/code&gt; they have not combined the &lt;code&gt;nuxt.config.ts&lt;/code&gt; and the &lt;code&gt;vite.config.ts&lt;/code&gt; together, so while you can use it, it's not quite ready yet. I also had issues with the &lt;code&gt;next-app&lt;/code&gt;. I wasn't sure if it was trying to use a Vite-based Next app or the standard one. Either way it wasn't quite working.&lt;/p&gt;

&lt;p&gt;After adding a template name it will then setup your app. It should look familiar if you're using something like &lt;code&gt;npm create vite&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;After install you'll then have a unified vite.config.ts file that you can then enter all your configurations for linting, formatting and testing. This worked surprisingly well and I was happy not having to worry about where to add each file.&lt;/p&gt;

&lt;p&gt;Since Vite+ uses the &lt;a href="https://oxc.rs/" rel="noopener noreferrer"&gt;Oxc&lt;/a&gt; set of tools, I added in the official Oxc extension for formatting and linting. This made it even easier so that my IDE was aware of these tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Important commands
&lt;/h2&gt;

&lt;p&gt;Beyond &lt;code&gt;vp create&lt;/code&gt; you have a number of interesting commands you can run with &lt;code&gt;vp&lt;/code&gt;. Here are a few I've found useful. Although, if you'd like a full list please check out the official &lt;a href="https://viteplus.dev/guide/" rel="noopener noreferrer"&gt;guide&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Checking and linting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vp check is the default command for fast static checks in Vite+. It combines both the formatting with Oxfmt and the linting with Oxlint, and the TypeScript type checks via &lt;a href="https://github.com/oxc-project/tsgolint" rel="noopener noreferrer"&gt;tsgolint&lt;/a&gt;. Combined this is an extremely fast static checker.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Under the hood, tsgolint is powered by &lt;a href="https://github.com/microsoft/typescript-go" rel="noopener noreferrer"&gt;tsgo&lt;/a&gt;, the official Go port of the TypeScript compiler from Microsoft. This means your type checking is running at native speed, not through Node.js. It's a big reason why &lt;code&gt;vp check&lt;/code&gt; feels so fast.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you like you can have it try to fix the issues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp check &lt;span class="nt"&gt;--fix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my tests, this still takes a lot of manual intervention as expected. What's nice is that a competent AI coding tool, like &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, can knock these type errors out in no time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Vite+ also has built in pre-commit hook support. When you scaffold a project with &lt;code&gt;vp create&lt;/code&gt;, it can set up hooks for you automatically. You can also run &lt;code&gt;vp prepare&lt;/code&gt; to install them later, and configure what runs on staged files right in your &lt;code&gt;vite.config.ts&lt;/code&gt;. No more husky or lint-staged setup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Formatting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp &lt;span class="nb"&gt;fmt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run the &lt;a href="https://oxc.rs/docs/guide/usage/formatter.html" rel="noopener noreferrer"&gt;Oxfmt&lt;/a&gt; formatter. It has full &lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt; compatibility and is designed as a drop-in replacement for Prettier.&lt;/p&gt;

&lt;p&gt;Personally, I love prettier, but it does have its quirks. What's really nice is you can just go into the &lt;code&gt;vite.config.ts&lt;/code&gt; file and add any rules you need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite-plus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;singleQuote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I first setup my app using &lt;code&gt;vp create vue&lt;/code&gt; it asked me if I'd like to update the &lt;code&gt;.vscode/settings.json&lt;/code&gt;. I'd highly recommend this so if you're using the Oxfmt extension it can find your formatting settings. Otherwise you can manually add it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"oxc.fmt.configPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./vite.config.ts"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;I love testing, so running tests with &lt;a href="https://vitest.dev/" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt; is great.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will kickoff the vitest test runner in your project. There is also a &lt;code&gt;watch&lt;/code&gt; and a &lt;code&gt;run --coverage&lt;/code&gt; command. And like all the other commands you can edit the configuration in the &lt;code&gt;vite.config.ts&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite-plus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/**/*.test.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;One quick annoyance with running the &lt;code&gt;vp&lt;/code&gt; commands is that they do override your package.json scripts. So to run anything in your scripts you must run &lt;code&gt;vp run &amp;lt;command&amp;gt;&lt;/code&gt;. Otherwise it will run the built in commands. I was watching a live stream with Theo Browne from &lt;a href="https://t3.gg/" rel="noopener noreferrer"&gt;T3&lt;/a&gt; and he hated this. I'm on the fence, but maybe they'll change it in the future.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Caching with vp run
&lt;/h3&gt;

&lt;p&gt;Another nice feature is caching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp run &lt;span class="nt"&gt;--cache&lt;/span&gt; build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will cache the build and if nothing changes the output is replayed on the next run. I have not used this command personally but anything to speed up builds I'm all for. &lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Node.js with vp env
&lt;/h3&gt;

&lt;p&gt;Another really neat feature is the &lt;code&gt;vp env&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp &lt;span class="nb"&gt;env&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vp env manages Node.js versions globally and per project. By default, Vite+ runs in managed mode, meaning it controls which Node.js version is used. It stores the runtime and related files in ~/.vite-plus (you can override this with VITE_PLUS_HOME).&lt;/p&gt;

&lt;p&gt;If you don't want Vite+ managing your Node.js, you can switch to system-first mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp &lt;span class="nb"&gt;env &lt;/span&gt;off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the shims to prefer your system Node.js and only fall back to the Vite+ managed runtime when needed. If you ever want to switch back, just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp &lt;span class="nb"&gt;env &lt;/span&gt;on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also pin a certain Node.js version in the current directory with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp &lt;span class="nb"&gt;env &lt;/span&gt;pin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or install an entirely new version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vp &lt;span class="nb"&gt;env install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I really liked all this flexibility with the package manager. I personally use &lt;code&gt;nvm&lt;/code&gt; but I like the idea of this being handled by Vite+. One less thing to worry about.&lt;/p&gt;

&lt;p&gt;While I haven't tried it, I'm curious to see how well this works in a CI environment. It looks like you can easily set this up in your GitHub actions and run all the checks you need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;voidzero-dev/setup-vp@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;22'&lt;/span&gt;
    &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vp install&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vp check&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vp test&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vp build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I could definitely see Vite+ saving me time when I create a new application. Having one configuration file to put everything in, and not having to think about which linter, formatter, or test runner to install, is a nice change. The &lt;code&gt;vp&lt;/code&gt; commands are straightforward and the whole thing just works.&lt;/p&gt;

&lt;p&gt;With that said, this project is still in alpha, and we'll see how much it catches on. With so much of the frontend ecosystem already using Vite, it makes sense for a tool like Vite+ to exist. Let me know if you would use something like this. I think it's very interesting. Thanks!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>vite</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>5 Things To Avoid When Working With AI Coding Tools</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Thu, 12 Mar 2026 21:10:42 +0000</pubDate>
      <link>https://dev.to/aws/5-things-to-avoid-when-working-with-ai-tools-5cld</link>
      <guid>https://dev.to/aws/5-things-to-avoid-when-working-with-ai-tools-5cld</guid>
      <description>&lt;p&gt;As a software developer in 2026 you can't escape AI. It's everywhere, and almost every company is using some sort of AI coding tool. And as a long time full-stack developer whose roots are in the front-end, I wasn't always convinced. &lt;/p&gt;

&lt;p&gt;Over the past two years I've seen both sides of AI. The terrible designs, but also the surprisingly decent ones. Demo apps that used to take me hours, done in minutes. But I've also found myself knee-deep in AI slop, wondering if I actually saved any time at all. &lt;/p&gt;

&lt;p&gt;So are these coding tools making me faster or not? I had to look into it more.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to watch a video on the subject then read about it? Check this out!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/LHWt5ybRFk0"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Last year, Model Evaluation and Threat Research, or METR, ran a &lt;a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer"&gt;randomized controlled trial&lt;/a&gt; and found that when experienced open source developers used AI tools, it took them 19% longer to complete tasks than without. AI was actually a detriment to their productivity.&lt;/p&gt;

&lt;p&gt;On the other hand, GitHub conducted their own &lt;a href="https://github.blog/news-insights/research/research-quantifying-github-copilots-impact-on-code-quality/" rel="noopener noreferrer"&gt;controlled trial&lt;/a&gt; around the same time and found that developers coded up to 55% faster with AI assistance.&lt;/p&gt;

&lt;p&gt;So which is it?&lt;/p&gt;

&lt;p&gt;After trying these tools out myself for the last two years, I can confidently say I think both are true. And the difference comes down to the approach used. AI tools, just like any other tool, can be misused. If the only tool you have is a hammer, every problem starts to look like a nail. When I first started using AI, I used it for everything. New features, bug fixes, refactors, brainstorming, all of it. And at first it felt amazing. But I kept running into the same problems over and over. The output looked good on the surface, but I'd spend more time fixing what it gave me than if I'd just written it myself.&lt;/p&gt;

&lt;p&gt;Here are 5 ways AI can hurt you instead of help you, especially on the frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Real Feature Definition
&lt;/h3&gt;

&lt;p&gt;I like jumping into a coding agent right away. I start vibe coding as soon as my fingers reach the keyboard, but this actually isn't a great way to start. The AI will absolutely give me what I ask for, and it might even work, but the problem lies in the details.&lt;/p&gt;

&lt;p&gt;The design generated is often not great (I'm looking at you GPT) and the features don't always work. Validation, error handling, responsiveness, and accessibility will often be partially implemented or skipped entirely. That's when things fall apart.&lt;/p&gt;

&lt;p&gt;The real issue is that you need to define what success looks like. If you don't, the AI just guesses, and though it gets some of it right, it gets a lot wrong. Ambiguity isn't your friend.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The real issue is that you need to define what success looks like.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't get me wrong, you don't need a 15 page requirements document with detailed designs for every use case. However, maybe a few bullet points and some basic acceptance criteria will help. Simply defining what the feature should do and not do is the minimum you should ask. Adding this all to a markdown file before you get started will dramatically improve what the AI generates for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F657u4j29532gajx7ja93.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F657u4j29532gajx7ja93.jpg" alt="Screen of code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Too Much Bad Context
&lt;/h3&gt;

&lt;p&gt;So that means I should put everything into the context window? Use as many AGENTS.md and markdown files as I can before I start, right? Well, yes we need more context but there is more to it than that.&lt;/p&gt;

&lt;p&gt;The second problem is putting too much into your context window. Whenever you work with a coding tool, I prefer &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; so I'll use that as an example, it has a defined amount of space it can handle. This is often influenced by the model you select. Some models have larger context windows, others have smaller.&lt;/p&gt;

&lt;p&gt;Either way, stuffing everything you can find into lots of markdown files causes the opposite problem from #1. Now the model has to comb through lots of useless information to find what it needs. We've recently seen research that shows that more is not always better.&lt;/p&gt;

&lt;p&gt;Recent research across over 60,000 repos found that context files are often too long, too vague, and are actively making agents worse. In one study, accuracy dropped from 87% to 54% just from context overload.&lt;/p&gt;

&lt;p&gt;Context overload doesn't just drop accuracy, it can also increase token cost. When every request sends more information than is needed, you uselessly waste tokens, which in turn hits your pocketbook.&lt;/p&gt;

&lt;p&gt;At the end of the day, it's more about quality than quantity when you're dealing with context windows. Think about it like giving directions. If someone asks you how to get to the grocery store and you hand them a 200-page atlas, that's technically more information. But it's not more helpful.&lt;/p&gt;

&lt;p&gt;When creating an AGENTS.md file (or steering file if you're using Kiro), only include information that is needed for your project. Constrain it to your coding practices, tabs vs spaces, design system, API contracts and rendering. Make sure to remove unneeded fluff, and keep it up to date. When I use Kiro, I create an agent that automatically updates my markdown files when I make changes to my components. That way I always have the latest updates in my context files.&lt;/p&gt;

&lt;p&gt;There's probably a Goldilocks zone of context. Not too little, not too much. Just the stuff that actually matters for the task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Too Much in One Shot
&lt;/h3&gt;

&lt;p&gt;While having too much or too little in your context is important, you still need to know the limitations of what you're asking your coding agent to do. One common problem I see is that developers will try to one-shot a whole application. I'll see prompts to build a frontend, backend, tests, create all the things at once. It feels like you're making a lot of progress, and it outputs something in just a few minutes.&lt;/p&gt;

&lt;p&gt;This could actually work for a quick prototype or demo. However, in production this is not what you want. You'll end up with a lot of AI slop that often takes a lot of rework.&lt;/p&gt;

&lt;p&gt;I was reading the other day that a whole industry has popped up to help small teams fix their vibe-coded messes. This should really tell you something about the state of the industry. &lt;/p&gt;

&lt;p&gt;The reality is that creating everything at once causes the AI to lose architectural consistency. It may solve the same problem different ways in different files. It creates patterns that are contradictory or nonsensical.&lt;/p&gt;

&lt;p&gt;To fix this issue, you need to scope down the tasks. Unless you're running some autonomous agent loop that's going to churn through a large detailed requirements document and tasks for hours, you'll need to break things down. Build this component, refactor these tests. Here are the edge cases for this part of the app. The key, as we'll discuss in the next section, is to check the output. It's a lot easier to catch a problem in 50 lines than 2,000.&lt;/p&gt;

&lt;p&gt;This problem has actually been mitigated in a lot of ways with spec-driven development. I use Kiro all the time to break down complex features into requirements, design and tasks. That way you can check the plan before the AI writes any code at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Too Much Trust
&lt;/h3&gt;

&lt;p&gt;As hinted in the last paragraph, we should not be trusting the output of AI at face value. And of course, it does look convincing at first. When I started using Claude 4.5 Opus, I really thought coding was a solved problem. The code passed my linter, the types looked ok, and it ran well. &lt;/p&gt;

&lt;p&gt;But as I started looking at the code in more detail, I saw some issues and edge cases I didn't like. Maybe one day AI will write 100% accurate code, but we still aren't there yet.&lt;/p&gt;

&lt;p&gt;There is no question AI makes writing code faster, but you need to spend more time checking the output. In other words, AI compresses generation time, but it often expands verification time. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI compresses generation time, but it often expands verification time. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'll be the first to admit, I love writing code, debugging it, finding clever ways to abstract the logic, and seeing it run for the first time. I'm not as big of a fan of code reviews. Something about reviewing other people's code and giving feedback isn't as fun.&lt;/p&gt;

&lt;p&gt;However, when AI is doing the writing, reviewing becomes the most important part of your job. For example, on the frontend I've seen things like weak accessibility, brittle logic, weird abstractions, duplicated behavior, and terrible design. Let me emphasize again that AI is not great at design. It's passable if you love purple and you don't mind generic Tailwind-looking apps. You really need to iterate over it several times to get a good outcome.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI is not great at design!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To help mitigate these problems you can set up tests, and those help, but sometimes the AI games those too. Really though, there is no better way than having a real life breathing human verify everything along the way. You should read the code, understand it, gauge the design, and test the assumptions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsp35d3jrrvx1zm53p5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsp35d3jrrvx1zm53p5r.png" alt="Asking to trust"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed Over Maintainability
&lt;/h3&gt;

&lt;p&gt;As a developer, I love writing code fast. I remember being a young software developer and learning as many hotkeys and keystrokes as I could. I learned Vim so I would never have to touch the mouse, because I knew that every second I touched it, it would slow me down.&lt;/p&gt;

&lt;p&gt;With AI writing your code, speed stops being the hard part. I'll never type faster than a large language model can generate. But that's the trap, just because it's fast doesn't mean it's good. &lt;br&gt;
When code gets cheap to generate, bad abstractions get cheaper too.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When code gets cheap to generate, bad abstractions get cheaper too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI will over-generate wrappers, components, abstractions and APIs. These AI tools are like eager interns, they want to show off what they can do. But if you don't monitor them closely they'll go off the rails. &lt;/p&gt;

&lt;p&gt;And my cognitive load can only handle so much. I remember one of the first codebases I worked on had so many levels of abstraction, it took me 15 minutes of ctrl-clicking into every class and interface to figure out what it was exactly doing. (Yes this was in Java). If you let AI go off, it will do the same thing. &lt;/p&gt;

&lt;p&gt;You end up owning code you didn't fully think through or really understand. Your job is to understand what the AI is creating, because six months from now, someone has to maintain it. And that someone is probably you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modern Tooling
&lt;/h3&gt;

&lt;p&gt;So where are we at today? Well, we know that models are getting better all the time, and maybe in a few years most of these problems will be solved.&lt;/p&gt;

&lt;p&gt;But we are not there yet.&lt;/p&gt;

&lt;p&gt;Tools like Kiro have spec-driven development, and other tools have plan mode, checkpoints, and better workflows. But these tools are only as good as the person driving them and there still is no silver bullet. You still need your judgement, your context, and your code review skills to be successful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;If we go back to our two studies at the start, both things can be true at once. Some developers will see productivity gains with AI tools, many others won't. It really comes down to how you use them in your day-to-day life.&lt;/p&gt;

&lt;p&gt;You can't vibe code every app, and every app shouldn't be vibe coded. You need the right amount of context, not too little, not too much. You need to scope your asks. You need to become a better code reviewer. And you need to think about whether what the AI created is something you can actually maintain six months from now.&lt;/p&gt;

&lt;p&gt;Let me know in the comments if you agree or disagree. Until next time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>coding</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The hosting setup nobody talks about anymore</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Fri, 20 Feb 2026 00:15:40 +0000</pubDate>
      <link>https://dev.to/aws/the-hosting-setup-nobody-talks-about-anymore-2528</link>
      <guid>https://dev.to/aws/the-hosting-setup-nobody-talks-about-anymore-2528</guid>
      <description>&lt;p&gt;Ever had this problem? &lt;/p&gt;

&lt;p&gt;&lt;em&gt;You're building something real, real-time features, background workers, cron jobs, maybe a database or two. You've outgrown the managed platforms, or you're tired of stitching together five different SaaS subscriptions to get what a single server could give you. You want to understand your infrastructure, not just deploy to it. Let me introduce you to the world of Virtual Private Servers (VPS).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A VPS gives you full control over a private server that you yourself manage. Root access, any library or service you want, no execution limits or timeouts. If something breaks, you can SSH in and fix it, no support tickets, no waiting.&lt;/p&gt;

&lt;p&gt;That flexibility comes with a tradeoff: you're responsible for the setup, the security, and the maintenance. Managed platforms abstract that away, which is exactly why they're great for simpler use cases. However when your app needs more than what a platform gives you out of the box, or when you just want to learn how the pieces fit together, running your own server is worth the investment.&lt;/p&gt;

&lt;p&gt;The purpose of this tutorial is to walk you through that setup end to end. We'll launch a VPS, configure a web server with a CDN, connect it to a domain, and wire up a deployment pipeline. By the end you'll understand every layer of your stack. Let's jump in!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up&lt;/strong&gt; — this tutorial gets you to a working deployment. If you plan to serve real user traffic, check the Production Hardening section before going live.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Our Setup&lt;/li&gt;
&lt;li&gt;
Step-by-step

&lt;ul&gt;
&lt;li&gt;Launching your EC2 instance&lt;/li&gt;
&lt;li&gt;Installing nginx and Docker&lt;/li&gt;
&lt;li&gt;Configuring AWS Systems Manager&lt;/li&gt;
&lt;li&gt;Setting up the deploy directory and ECR&lt;/li&gt;
&lt;li&gt;Connecting GitHub with OIDC&lt;/li&gt;
&lt;li&gt;Adding the GitHub Action and Dockerfile&lt;/li&gt;
&lt;li&gt;Testing the deployment&lt;/li&gt;
&lt;li&gt;Setting up CloudFront, SSL, and nginx&lt;/li&gt;
&lt;li&gt;Connecting your domain with Route 53&lt;/li&gt;
&lt;li&gt;Adding cache behaviors&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Production hardening&lt;/li&gt;

&lt;li&gt;Cleanup&lt;/li&gt;

&lt;li&gt;Wrapping up&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Want a video instead? &lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/VKk-l97CNU8"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Setup
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Full disclosure, I'm a &lt;a href="https://www.linkedin.com/in/erikhanchett/" rel="noopener noreferrer"&gt;Developer Advocate for AWS&lt;/a&gt;, so I'll be using AWS services in the tutorial as it's what I'm most familiar with. I've been hosting web apps on VPS for years. Feel free to use whatever VPS provider you'd like though.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this post we are using an &lt;a href="https://aws.amazon.com/ec2/" rel="noopener noreferrer"&gt;Amazon EC2&lt;/a&gt; T3 Micro instance running Ubuntu with an nginx web server. We'll use &lt;a href="https://aws.amazon.com/systems-manager/" rel="noopener noreferrer"&gt;AWS Systems Manager&lt;/a&gt; to help set up a CI/CD pipeline using GitHub Actions. We'll then configure &lt;a href="https://aws.amazon.com/certificate-manager/" rel="noopener noreferrer"&gt;AWS Certificate Manager&lt;/a&gt; with &lt;a href="https://aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;Amazon CloudFront&lt;/a&gt; and have it connected to our domain with &lt;a href="https://aws.amazon.com/route53/" rel="noopener noreferrer"&gt;Amazon Route 53&lt;/a&gt;! We'll be using a Vue Nuxt 4 application as our web app. &lt;/p&gt;

&lt;p&gt;Here is a high level diagram of our final output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgth23uog5ca1rrhov3yr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgth23uog5ca1rrhov3yr.png" alt="Architecture of application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Don't worry, you won't need to be an EC2 or AWS expert to follow this post. I'll assume you have some basic knowledge of software development, but that's it. To get started make sure you sign up for a &lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;free AWS account&lt;/a&gt;. I'll also assume you have some sort of application you want to deploy and it's already on &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. In this example we'll be using Nuxt with SSR, but you can use whatever you'd like. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step
&lt;/h2&gt;

&lt;p&gt;Typically in enterprise applications you might see infrastructure as code (IAC) building and deploying applications on AWS. However, for this tutorial we'll be going directly to the AWS console. I find this the simplest way to get started.&lt;/p&gt;

&lt;p&gt;If you're following along with this tutorial, or skimming it through to get ideas, make sure to download a good agentic IDE like &lt;a href="https://kiro.dev/cli/" rel="noopener noreferrer"&gt;Kiro CLI&lt;/a&gt;. Kiro CLI can be installed remotely on your server and help you troubleshoot any production issue you have while setting up your service. I used it extensively while researching this blog post, and you should too! &lt;/p&gt;

&lt;h3&gt;
  
  
  Launching your EC2 instance
&lt;/h3&gt;

&lt;p&gt;To host our application we'll start by creating a new EC2 instance. Log into the &lt;a href="https://console.aws.amazon.com/" rel="noopener noreferrer"&gt;AWS Console&lt;/a&gt; to begin. Make sure you're on &lt;code&gt;N. Virginia us-east-1&lt;/code&gt; in the top right-hand corner.&lt;/p&gt;

&lt;p&gt;Search for &lt;code&gt;EC2&lt;/code&gt; in your AWS Console. It will bring you to a page where you can click &lt;code&gt;Launch instance&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71861xer41aujhlw01ph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71861xer41aujhlw01ph.png" alt="AWS EC2 console showing the Launch instance button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll then be brought to a screen with a lot of options. Don't worry, just fill out the name of your server. In this case I chose &lt;code&gt;My-Web-Server&lt;/code&gt; and pick an OS image. I really like Ubuntu for beginners so I chose that. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcblmn84upi8r1o0aear.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcblmn84upi8r1o0aear.png" alt="EC2 launch wizard showing server name and Ubuntu OS image selection"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next you'll need to select an instance type. We want to keep this server cheap (and free-tier eligible), so let's go with the t3.micro instance that has 2 vCPUs and 1 GiB of memory. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsootdq6gv3dirqanpr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsootdq6gv3dirqanpr4.png" alt="EC2 instance type selection showing t3.micro with 2 vCPUs and 1 GiB memory"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What about traffic?&lt;/strong&gt;&lt;br&gt;
I know what you're thinking, how much traffic can a t3.micro instance handle? While I can't say for sure, we'll be using a Content Delivery Network (CDN) via Amazon CloudFront and caching as much as possible to help absorb traffic spikes. And if you ever outgrow a single instance, AWS can handle it. An Auto Scaling Group (ASG) can spin up additional EC2 instances once a certain traffic threshold is met, an Application Load Balancer (ALB) can distribute incoming traffic across them, and a Web Application Firewall (WAF) can block malicious or "noisy" traffic. We won't be covering those in this post, but I'd recommend reading up on it &lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/tutorial-ec2-auto-scaling-load-balancer.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you're interested.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click the &lt;code&gt;Create new key pair&lt;/code&gt; button. This will send you to a popup to add a new key. Add a new key pair name, leave the rest as default and click &lt;code&gt;Create key pair&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft448qnpux6eom810gzxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft448qnpux6eom810gzxh.png" alt="Create key pair dialog with name field and default settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will then ask you to save the key. This is very important. You'll need this key to SSH into your instance later. Save it somewhere safe.&lt;/p&gt;

&lt;p&gt;In the network settings you may see a banner that you don't have a default VPC. Click &lt;code&gt;create a new VPC&lt;/code&gt;. On the next page leave everything as default and click &lt;code&gt;Create default VPC&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2459bbzzggoe7f79shdz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2459bbzzggoe7f79shdz.png" alt="Banner prompting to create a default VPC"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now be able to select your VPC, if it's not already selected. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1164hc59nscvkmrdm3a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1164hc59nscvkmrdm3a.png" alt="Network settings with VPC selected"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Firewall (security groups) make sure to change the &lt;code&gt;Allow SSH traffic from&lt;/code&gt; to only allow traffic from your IP. It should be listed in the dropdown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F701xhvftf78oitxycrtz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F701xhvftf78oitxycrtz.png" alt="Firewall settings restricting SSH traffic to your IP address"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the bottom click on &lt;code&gt;Launch instance&lt;/code&gt; and you'll see a nice green &lt;code&gt;Success&lt;/code&gt; banner. You can click on your instance &lt;code&gt;i-***&lt;/code&gt; to look at it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8cfwdara33yshwpy7a6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8cfwdara33yshwpy7a6.png" alt="Success banner after launching the EC2 instance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next window look for the public IP address. Save it! &lt;/p&gt;

&lt;p&gt;Now jump into your favorite terminal. Make sure you're in the same directory as the &lt;code&gt;*.pem&lt;/code&gt; file you created earlier. You'll need to set some permissions on it. You'll then be able to SSH into your server. Make sure to replace &lt;code&gt;ip-address&lt;/code&gt; with the public IP address you saved from the EC2 instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;400 your-pem-file.pem
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; your-pem-file.pem ubuntu@ip-address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉 Congrats! You've logged in for the first time!&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing nginx and Docker
&lt;/h3&gt;

&lt;p&gt;Now that we have our EC2 instance up and running, let's do something with it!&lt;/p&gt;

&lt;p&gt;First update and then upgrade Ubuntu to the latest. It may ask you to restart some services. You may consider some best &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-best-practices.html" rel="noopener noreferrer"&gt;practices&lt;/a&gt; when working with EC2 as well.&lt;/p&gt;

&lt;p&gt;Let's install nginx. This will be our web server that we'll use as a reverse proxy. It listens on port 80 for incoming traffic and forwards it to our Nuxt app running on port 3000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After nginx is installed we'll need to do some configuration. Let's add a new site under the &lt;code&gt;sites-available&lt;/code&gt; directory. We'll call it &lt;code&gt;nuxt&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/nginx/sites-available/nuxt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace it with the code below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;# (we’ll enable this after CloudFront is created)&lt;/span&gt;
  &lt;span class="c1"&gt;# if ($http_x_origin_verify != "REPLACE_WITH_SECRET") { return 403; }&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a basic setup for nginx. Check out the &lt;a href="https://nginx.org/en/docs/beginners_guide.html" rel="noopener noreferrer"&gt;beginners guide&lt;/a&gt; for more information on what each option does. The &lt;code&gt;Upgrade&lt;/code&gt; and &lt;code&gt;Connection&lt;/code&gt; headers are there so WebSocket connections work correctly — handy if your app uses real-time features. &lt;/p&gt;

&lt;p&gt;You may have noticed a comment at the top! Don't worry, we'll come back to this later when we set up CloudFront.&lt;/p&gt;

&lt;p&gt;To enable the site we have to create a symbolic link from &lt;code&gt;sites-available&lt;/code&gt; to &lt;code&gt;sites-enabled&lt;/code&gt;. We'll also do a little cleanup and remove the default nginx site and restart it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; /etc/nginx/sites-available/nuxt /etc/nginx/sites-enabled/nuxt
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /etc/nginx/sites-enabled/default
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all goes well we'll get a successful message! If not, double-check the &lt;code&gt;nuxt&lt;/code&gt; configuration inside nginx. It's easy to copy something wrong.&lt;/p&gt;

&lt;p&gt;At this point we have a few options to handle our Nuxt site. We could just copy the dist folder over, install node, and use something like &lt;a href="https://pm2.keymetrics.io/" rel="noopener noreferrer"&gt;pm2&lt;/a&gt; to manage the node process. While this works, I find it a little brittle. &lt;/p&gt;

&lt;p&gt;Running everything on our EC2 instance requires managing dependencies directly on the system, making it harder to ensure consistent environments between development and production. Docker containers provide a better option. They isolate our dependencies, we can roll back easier, and we'll have more predictable deployments since everything your app needs is packaged together.&lt;/p&gt;

&lt;p&gt;To install Docker we'll follow the official &lt;a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener noreferrer"&gt;Docker docs&lt;/a&gt; that recommend using the &lt;code&gt;apt&lt;/code&gt; repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add Docker's official GPG key:&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ca-certificates curl
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings
&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.asc
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.asc

&lt;span class="c"&gt;# Add the repository to Apt sources:&lt;/span&gt;
&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.sources &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UBUNTU_CODENAME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code allows us to install Docker under apt. Let's do so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's verify it's working. &lt;strong&gt;Always use &lt;code&gt;sudo&lt;/code&gt; to run docker commands&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run hello-world

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring AWS Systems Manager
&lt;/h3&gt;

&lt;p&gt;AWS Systems Manager (SSM) agent should already be installed on the instance; however, in case it isn't, you can use &lt;code&gt;snap&lt;/code&gt; to install it. We'll then run a &lt;code&gt;systemctl&lt;/code&gt; command to start the service. &lt;/p&gt;

&lt;p&gt;SSM is a comprehensive management service that provides a unified user interface for tracking and resolving operational issues across AWS and hybrid cloud environments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;amazon-ssm-agent &lt;span class="nt"&gt;--classic&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;snap.amazon-ssm-agent.amazon-ssm-agent.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start snap.amazon-ssm-agent.amazon-ssm-agent.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can verify it's running by checking the status.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status snap.amazon-ssm-agent.amazon-ssm-agent.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back inside the console open up the EC2 instance. Use the &lt;code&gt;Actions&lt;/code&gt; menu to modify the attached IAM role.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxyifhbvyi31ihf44trx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxyifhbvyi31ihf44trx.png" alt="EC2 Actions menu showing the option to modify the IAM role"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to attach the AmazonSSMManagedInstanceCore and AmazonEC2ContainerRegistryReadOnly policies. The AmazonSSMManagedInstanceCore policy allows the EC2 instance to be managed by AWS Systems Manager, enabling remote access and command execution without SSH. The AmazonEC2ContainerRegistryReadOnly policy grants the instance permission to pull Docker images from &lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;Amazon Elastic Container Registry (ECR)&lt;/a&gt; repositories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqhppx5wjiq0abp10yuac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqhppx5wjiq0abp10yuac.png" alt="IAM role with AmazonSSMManagedInstanceCore and AmazonEC2ContainerRegistryReadOnly policies attached"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the new role and click the &lt;code&gt;Update IAM role&lt;/code&gt; button to complete the process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfviw8hn0d7was7iaia7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfviw8hn0d7was7iaia7.png" alt="Update IAM role confirmation screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you like, you can check Systems Manager in the console to see the EC2 instance connected. Search for Systems Manager -&amp;gt; Fleet Manager  and you'll see the instance connected. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mwxv1vid3t3snf8iciy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mwxv1vid3t3snf8iciy.png" alt="Systems Manager Fleet Manager showing the EC2 instance connected"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the deploy directory and ECR
&lt;/h3&gt;

&lt;p&gt;For our app deployment, we'll be hosting our images on ECR. We'll need a script that will be triggered by SSM to pull the &lt;code&gt;latest&lt;/code&gt; tag and run it.  &lt;/p&gt;

&lt;p&gt;To do this we'll create a new directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/nuxt-app
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;ubuntu:ubuntu /opt/nuxt-app
&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/nuxt-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create the &lt;code&gt;docker-compose.yml&lt;/code&gt; file inside the &lt;code&gt;/opt/nuxt-app&lt;/code&gt; folder. Don't worry about the image, we'll replace that later. Also go ahead and create an empty &lt;code&gt;.env&lt;/code&gt; file now — Docker Compose will fail if the &lt;code&gt;env_file&lt;/code&gt; path doesn't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; /opt/nuxt-app/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nuxt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_LATER&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/opt/nuxt-app/.env&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll need to install the AWS CLI so we can pull down the deployed image from ECR. Install the latest version from the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;unzip
curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As always, make sure it works by running the commands below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws &lt;span class="nt"&gt;--version&lt;/span&gt;
aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will verify that AWS CLI is working and that the permissions we set earlier in the IAM policy are there.&lt;/p&gt;

&lt;p&gt;Let's now configure ECR!&lt;/p&gt;

&lt;p&gt;Inside the AWS console head to ECR → Repositories → Create repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcjoef2hup21wjbopmv6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcjoef2hup21wjbopmv6.png" alt="ECR console showing the Create repository page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a name and create it. In this case I'll use the postfix &lt;code&gt;ec2/host&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpt9rsiv4ettew8pe9ygj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpt9rsiv4ettew8pe9ygj.png" alt="ECR repository created with the ec2/host name"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make sure to write down the full ECR repository name for later.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting GitHub with OIDC
&lt;/h3&gt;

&lt;p&gt;Now let's set up an OIDC provider for GitHub. Later we'll create a GitHub Action that will need access to our AWS account to work. To get more information on how OIDC providers work in GitHub, feel free to check out this &lt;a href="https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws" rel="noopener noreferrer"&gt;guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Inside the AWS console head to IAM → Identity Providers → Add provider.&lt;/p&gt;

&lt;p&gt;On this page make sure to select &lt;code&gt;OpenID Connect&lt;/code&gt;, use the URL &lt;code&gt;https://token.actions.githubusercontent.com&lt;/code&gt; and the audience as &lt;code&gt;sts.amazonaws.com&lt;/code&gt;. It should look like the screenshot below! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2doo6e2iagdfkgy22tou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2doo6e2iagdfkgy22tou.png" alt="IAM Identity Provider setup with OpenID Connect for GitHub Actions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's create a new role for this.&lt;/p&gt;

&lt;p&gt;Inside the AWS console head to IAM → Roles → Create role.&lt;/p&gt;

&lt;p&gt;Create a new IAM role connecting to this provider. Choose &lt;code&gt;Web identity&lt;/code&gt; and make sure to use the new identity provider you just created. Type in the GitHub organization and repository. If you like, you can also select the branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1xyqp7es4l14vkix1oh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1xyqp7es4l14vkix1oh.png" alt="IAM role creation with Web identity provider and GitHub organization and repo fields"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click next a few times and create the role! We aren't done yet though — we need to make sure this role has permissions to ECR so the GitHub Action can trigger our deployment workflow.&lt;/p&gt;

&lt;p&gt;Under permissions click the &lt;code&gt;Create inline policy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favmrm80ggttxig8g8vq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favmrm80ggttxig8g8vq1.png" alt="IAM role permissions tab with Create inline policy button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select JSON and copy and paste this in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ECRAuth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecr:GetAuthorizationToken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ECRPush"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:BatchCheckLayerAvailability"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:CompleteLayerUpload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:InitiateLayerUpload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:PutImage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:UploadLayerPart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ecr:BatchGetImage"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ecr:YOUR-REGION:YOUR-ACCOUNT-ID:repository/YOUR-REPO-NAME"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SSMRunCommand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ssm:SendCommand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ssm:*:*:document/AWS-RunShellScript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ec2:YOUR-REGION:YOUR-ACCOUNT-ID:instance/i-xxxxxxxxxxxxxxxxx"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to update the instance with your instance ID. You can find that in your EC2 console. It starts with &lt;code&gt;i-&lt;/code&gt;. You also need to update YOUR-REGION (e.g. us-east-1) and &lt;code&gt;YOUR-ACCOUNT-ID&lt;/code&gt; with your account ID. Also update &lt;code&gt;YOUR-REPO-NAME&lt;/code&gt; with your ECR repo name. In my case I called it &lt;code&gt;ec2/host&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Setting up for production&lt;/strong&gt;&lt;br&gt;
We're using &lt;code&gt;AWS-RunShellScript&lt;/code&gt; here which lets the CI pipeline run any shell command on your instance. That's great for a tutorial, however a compromised GitHub Actions workflow or malicious PR could achieve remote code execution on your host. In production, create a &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-doc-syntax.html" rel="noopener noreferrer"&gt;custom SSM document&lt;/a&gt; that only runs your deploy script. That way even if the GitHub role is compromised, it can only trigger the specific deployment, not arbitrary commands on your box. A minimal custom document looks like this:&lt;/p&gt;


&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schemaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deploy Nuxt app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mainSteps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws:runShellScript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deploy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"runCommand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sudo bash /opt/nuxt-app/deploy.sh"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Then reference your custom document name in the GitHub Action instead of &lt;code&gt;AWS-RunShellScript&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click create, name it &lt;code&gt;github-deploy-nuxt&lt;/code&gt;, and you are good to go.&lt;/p&gt;

&lt;p&gt;Make sure to copy the ARN for later! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcflxkbp9qj1uxulqvy5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcflxkbp9qj1uxulqvy5t.png" alt="IAM role summary showing the ARN to copy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now inside our GitHub repo let's add the GitHub variables.&lt;/p&gt;

&lt;p&gt;Open up your GitHub repo and head to Settings → Secrets and Variables → Actions → Variables.&lt;/p&gt;

&lt;p&gt;Add five new environment variables. These will be used in our GitHub Action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnth1qntd4qlqhkjymfe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnth1qntd4qlqhkjymfe.png" alt="GitHub Actions variables page with AWS environment variables configured"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add each variable one by one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_REGION  us-west-2
AWS_ACCOUNT_ID  your account id
ECR_REPO    ec2/host
INSTANCE_ID i-xxxxxx
AWS_DEPLOY_ROLE_ARN role ARN from above
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding the GitHub Action and Dockerfile
&lt;/h3&gt;

&lt;p&gt;For the deployment to work successfully we need to set up a GitHub Action that will deploy our app. Let's do that now.&lt;/p&gt;

&lt;p&gt;In your repo add a new &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt; file. This will be run whenever a push occurs to main.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Nuxt SSR&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.AWS_DEPLOY_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.AWS_REGION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to ECR&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecr-login@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push Docker image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;IMAGE_URI=${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/${{ vars.ECR_REPO }}&lt;/span&gt;

          &lt;span class="s"&gt;docker build -t $IMAGE_URI:prod .&lt;/span&gt;
          &lt;span class="s"&gt;docker push $IMAGE_URI:prod&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy via SSM&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;COMMAND_ID=$(aws ssm send-command \&lt;/span&gt;
            &lt;span class="s"&gt;--instance-ids "${{ vars.INSTANCE_ID }}" \&lt;/span&gt;
            &lt;span class="s"&gt;--document-name "AWS-RunShellScript" \&lt;/span&gt;
            &lt;span class="s"&gt;--parameters '{"commands":["sudo bash /opt/nuxt-app/deploy.sh"]}' \&lt;/span&gt;
            &lt;span class="s"&gt;--query "Command.CommandId" --output text)&lt;/span&gt;

          &lt;span class="s"&gt;aws ssm wait command-executed \&lt;/span&gt;
            &lt;span class="s"&gt;--command-id "$COMMAND_ID" \&lt;/span&gt;
            &lt;span class="s"&gt;--instance-id "${{ vars.INSTANCE_ID }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This action will assume our AWS credentials based on the GitHub role we just created. It will then build a Docker image and push it to ECR. We'll then trigger SSM to run our script that will complete the deployment.&lt;/p&gt;

&lt;p&gt;For us to build our Docker image, we'll need a Dockerfile in the root of our repo. Head to the repo and in the root add a new &lt;code&gt;Dockerfile&lt;/code&gt;. This will build our Nuxt application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# --- build stage ---&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-bookworm-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# --- runtime stage ---&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-bookworm-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/.output ./.output&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", ".output/server/index.mjs"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll also need a &lt;code&gt;.dockerignore&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.nuxt
.output
.git
.gitignore
.env
.env.*
*.pem
*.md
.vscode
.idea

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we get too far, let's test it out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the deployment
&lt;/h3&gt;

&lt;p&gt;We are going to test our deployment, but first let's temporarily open up port 3000 on our EC2 host. We can then connect to the IP address later to see if our deployment works.&lt;/p&gt;

&lt;p&gt;Inside the AWS console head to EC2 → Security Groups → Inbound rules.&lt;/p&gt;

&lt;p&gt;Click on the security group.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F907q65rlyxpvik8i2n2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F907q65rlyxpvik8i2n2u.png" alt="EC2 security group showing inbound rules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Edit inbound rules and add your IP address on port 3000 on &lt;code&gt;Custom&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vmiv4u1y1uoi9kwd9xf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vmiv4u1y1uoi9kwd9xf.png" alt="Inbound rules editor with custom TCP rule for port 3000 restricted to your IP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's head back to the Docker Compose file again. This is important — before you push anything to GitHub, update the &lt;code&gt;docker-compose.yml&lt;/code&gt; with your real ECR image URI. If you skip this step the deploy will fail because Docker won't know what image to pull. Add in the ECR image name and set the port to 3000. Make sure to add the &lt;code&gt;:prod&lt;/code&gt; tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nuxt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;account-id&amp;gt;.dkr.ecr.us-east-1.amazonaws.com/ec2/host:prod&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/opt/nuxt-app/.env&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For my app I use several secrets; however, as you can see from earlier, our Docker ignore file excludes .env files (for good reason). Create a new &lt;code&gt;.env&lt;/code&gt; file with the secrets. This will be injected into our docker image later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /opt/nuxt-app/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next is the deploy script!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /opt/nuxt-app/deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GitHub action will run this on every deploy to main.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/nuxt-app

aws ecr get-login-password &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--region&lt;/span&gt; YOUR_REGION | &lt;span class="se"&gt;\&lt;/span&gt;
docker login &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--password-stdin&lt;/span&gt; YOUR_ACCOUNT_ID.dkr.ecr.YOUR_REGION.amazonaws.com

docker compose pull
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Don't use .env files in production&lt;/strong&gt;&lt;br&gt;
Plaintext &lt;code&gt;.env&lt;/code&gt; files on disk have no rotation, no audit trail, and no access control. For production, use &lt;a href="https://aws.amazon.com/secrets-manager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt; or &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html" rel="noopener noreferrer"&gt;Systems Manager Parameter Store&lt;/a&gt; (SecureString) to manage application secrets and pull them at runtime. The &lt;code&gt;.env&lt;/code&gt; approach shown here is suitable for development and tutorials only. You could also set up a blue/green deployment — spin up the new container, health-check it, then swap traffic only if healthy. For the purpose of this tutorial though, we'll keep it simpler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Replace the &lt;code&gt;YOUR_ACCOUNT_ID&lt;/code&gt; with your account id and &lt;code&gt;YOUR_REGION&lt;/code&gt; with your region.&lt;/p&gt;

&lt;p&gt;Then make it executable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are finally ready to try it all out! Push all your changes from your repo to GitHub and check the &lt;code&gt;Actions&lt;/code&gt; tab at the top and see it deploying!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu3d6gtq82uz8avabshi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu3d6gtq82uz8avabshi.png" alt="GitHub Actions tab showing a successful deployment workflow run"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Troubleshooting&lt;/strong&gt;&lt;br&gt;
If you see errors at this point, you'll need to double-check your GitHub role and that everything is connected. This might be a good time to use Kiro to help troubleshoot the problem!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can check if everything works by loading up port 3000 with the public IP address of the EC2 instance.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;http://&amp;lt;your-ec2-ip-address&amp;gt;:3000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Congrats 🥳! You now have a working deployment pipeline and your container is working!&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up CloudFront, SSL, and nginx
&lt;/h3&gt;

&lt;p&gt;Our pipeline is working; however, we need to revert the ports change we made in the &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Change back the &lt;code&gt;/opt/nuxt-app/docker-compose.yml&lt;/code&gt; to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nuxt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xxxx.dkr.ecr.us-east-1.amazonaws.com/ec2/host:prod&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/opt/nuxt-app/.env&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then apply it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/nuxt-app
&lt;span class="nb"&gt;sudo &lt;/span&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Remove port 3000 from your security group now.&lt;/strong&gt; It exposes your raw application server to the internet, bypassing nginx. This was only needed for testing — don't leave it open.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see if it works on the normal port 80.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;http://&amp;lt;your-ec2-ip-address&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If all goes well you should be seeing your website! 🎉&lt;/p&gt;

&lt;p&gt;But now we need CloudFront to add a Content Delivery Network (CDN) that will cache our static assets globally and improve performance for users worldwide. CloudFront also provides additional security features and helps protect our origin server from direct access.&lt;/p&gt;

&lt;p&gt;Let's assume you already have a domain in &lt;a href="https://aws.amazon.com/route53/" rel="noopener noreferrer"&gt;Route53&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Let's request a public certificate so we can add in SSL.&lt;/p&gt;

&lt;p&gt;In the AWS Console head to Certificate Manager → switch region to us-east-1 → Request certificate →  Request a public certificate .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsnvk0245n94mz80p73u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsnvk0245n94mz80p73u.png" alt="Certificate Manager request page for a public SSL certificate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then add your fully qualified domain (e.g. yourdomain.com). And add another for www (e.g. &lt;a href="http://www.yourdomain.com" rel="noopener noreferrer"&gt;www.yourdomain.com&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Leave everything else defaulted and click &lt;code&gt;Request&lt;/code&gt;. Wait a few minutes for the certificate to be issued.&lt;/p&gt;

&lt;p&gt;Inside the AWS Console head to CloudFront and create a new distribution.&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Create Distribution&lt;/code&gt;. Choose the free plan. Make sure to enter the distribution name, the domain name, and the &lt;code&gt;Domain to serve&lt;/code&gt; (www). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hmtlrq8gnylu3a22awp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2hmtlrq8gnylu3a22awp.png" alt="CloudFront Create Distribution page with distribution name, domain, and www settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next page choose &lt;code&gt;Other&lt;/code&gt;. For origin, type in the public DNS of the EC2 instance. You may need to go back to grab it. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs9l3w301rwe2f298277y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs9l3w301rwe2f298277y.png" alt="CloudFront origin settings with EC2 public DNS as the origin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the origin settings to customize origin settings. Then set an X-Origin-Verify header with a random secret you create.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why this header matters&lt;/strong&gt;&lt;br&gt;
The CloudFront Origin Header is extremely important. This ensures that your origin server can only be accessed through CloudFront and not directly from the internet, providing an additional layer of security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Use HTTPS to your origin in production&lt;/strong&gt;&lt;br&gt;
We're using HTTP for the origin protocol here since CloudFront handles HTTPS for your users. However, the X-Origin-Verify shared secret is transmitted in plaintext over this connection. Even with the security group locked to the CloudFront prefix list, traffic between CloudFront edge nodes and your EC2 instance traverses the public internet and could be intercepted. For production workloads, install a certificate on your instance (a self-signed cert works fine since CloudFront doesn't validate origin certs by default, or use &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt; for a free Let's Encrypt certificate), switch nginx to listen on 443, update CloudFront's origin protocol to HTTPS-only, and move your prefix list security group rule from port 80 to 443. This encrypts the entire path and keeps the shared header as a defense-in-depth measure rather than your primary access control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0brv0gd0mscbfkjzbbup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0brv0gd0mscbfkjzbbup.png" alt="Custom origin header configuration with X-Origin-Verify secret"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For cache settings, choose "CachingDisabled" as we'll configure specific caching behaviors for different content types after creating the distribution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9fjarva3lhg4idcgu0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9fjarva3lhg4idcgu0x.png" alt="CloudFront cache settings with CachingDisabled selected"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the Web Application Firewall (WAF) settings, don't make any additional changes.&lt;/p&gt;

&lt;p&gt;On the settings page, select the SSL certificate you created earlier from the dropdown menu. Make sure both your domain and www subdomain are covered.&lt;/p&gt;

&lt;p&gt;Review all your configuration settings and click "Create distribution". CloudFront will take several minutes to deploy globally — you'll see the status change from "Deploying" to "Enabled" when it's ready.&lt;/p&gt;

&lt;p&gt;After it's enabled, add a new behavior. Click &lt;code&gt;Create behavior&lt;/code&gt;. Fill out the &lt;code&gt;Path pattern&lt;/code&gt; as &lt;code&gt;/_nuxt/*&lt;/code&gt;, set the &lt;code&gt;Origin and origin groups&lt;/code&gt; dropdown to the EC2 instance. Make sure to set the &lt;code&gt;Viewer protocol policy&lt;/code&gt; to &lt;code&gt;Redirect HTTP to HTTPS&lt;/code&gt; and set the &lt;code&gt;Allowed HTTP methods&lt;/code&gt; to &lt;code&gt;GET, HEAD&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It should look like this at the end&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscnwoyttnu5og8j2el48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscnwoyttnu5og8j2el48.png" alt="CloudFront behavior for /_nuxt/* path pattern with HTTPS redirect and GET HEAD methods"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that CloudFront is in place, let's update the nginx configuration with the new secret key we created earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/nginx/sites-available/nuxt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$http_x_origin_verify&lt;/span&gt; &lt;span class="s"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"REPLACE_WITH_SECRET")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the &lt;code&gt;REPLACE_WITH_SECRET&lt;/code&gt; with the variable you set up in CloudFront. &lt;/p&gt;

&lt;p&gt;Then reload nginx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if you test the origin you should get a 403 forbidden.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;http://&amp;lt;your-ec2-ip-address&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Lock down port 80&lt;/strong&gt;&lt;br&gt;
For an extra layer of security, go to your EC2 security group and delete the existing port 80 inbound rule, then add a new one with the source set to the AWS-managed prefix list &lt;code&gt;com.amazonaws.global.cloudfront.origin-facing&lt;/code&gt;. This restricts port 80 so only CloudFront's network can reach your instance. Combined with the origin verify header, you now have two layers of protection on your origin.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🔥 We are getting one step closer!&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting your domain with Route 53
&lt;/h3&gt;

&lt;p&gt;Now you need to connect your CloudFront distribution to your domain through Route53.&lt;/p&gt;

&lt;p&gt;Go to Route53 in the AWS console and navigate to your hosted zone for your domain. Click "Create record" to add a new A record that will point your domain to the CloudFront distribution.&lt;/p&gt;

&lt;p&gt;Make sure you add a record. Choose &lt;code&gt;Alias&lt;/code&gt; and then choose &lt;code&gt;Alias to CloudFront distribution&lt;/code&gt; from the dropdown. Select your CloudFront distribution from the list. Leave the Record name blank for the root domain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrhdvp4gbx35ic6ed5b2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrhdvp4gbx35ic6ed5b2.png" alt="Route 53 A record aliased to the CloudFront distribution for the root domain"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add another A record, but this time for the &lt;code&gt;www&lt;/code&gt; subdomain.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8syomaa6qgq6499hv7q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8syomaa6qgq6499hv7q.png" alt="Route 53 A record for the www subdomain aliased to CloudFront"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It might take a few minutes for DNS propagation to complete, but your site should be accessible at your domain shortly after creating the A records.&lt;/p&gt;

&lt;p&gt;You can test the CloudFront distribution directly using its domain name while waiting for DNS to propagate. Once everything is working, visit your domain and you should see your site served securely over HTTPS!&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;https://&amp;lt;your-domain&amp;gt;&lt;/code&gt; 🎇&lt;/p&gt;

&lt;p&gt;You can even open Chrome DevTools and check the Network tab to see CloudFront cache hits in the response headers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf7mqnnp30ghru21c72l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf7mqnnp30ghru21c72l.png" alt="Chrome DevTools Network tab showing CloudFront cache hit headers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding cache behaviors
&lt;/h3&gt;

&lt;p&gt;If you'd like, we can add additional caching to our CloudFront distribution.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/favicon.ico&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/robots.txt&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;code&gt;/sitemap.xml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/images/*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these files can have their own caching rules. Static assets can be cached for longer periods, while you might want to disable caching entirely for server-side rendered (SSR) routes in Nuxt to ensure dynamic content is always fresh.&lt;/p&gt;

&lt;p&gt;To add these behaviors, go back to your CloudFront distribution and create new behaviors for each path pattern, adjusting the cache settings based on how frequently the content changes.&lt;/p&gt;

&lt;p&gt;Once everything is working properly, it's also a good security practice to remove SSH access from your EC2 instance's security group. Since we set up AWS Systems Manager earlier, you can still connect to your instance anytime through the SSM Session Manager in the AWS console — no SSH key needed, no port 22 open to the internet. Just head to your EC2 security group, delete the inbound rule for port 22, and save.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production hardening
&lt;/h2&gt;

&lt;p&gt;What we've built so far is a solid working deployment — but it's tutorial-grade. Before serving real user traffic, here are the things you'll want to tighten up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets management.&lt;/strong&gt; Replace the flat &lt;code&gt;.env&lt;/code&gt; file with &lt;a href="https://aws.amazon.com/secrets-manager/" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt; or &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html" rel="noopener noreferrer"&gt;SSM Parameter Store&lt;/a&gt; (SecureString). This gives you rotation, audit trails, and fine-grained access control instead of plaintext on disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Origin encryption.&lt;/strong&gt; Install a TLS certificate on your instance (self-signed works since CloudFront doesn't validate origin certs by default, or use &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt; for a free Let's Encrypt certificate), switch nginx to listen on 443, and set CloudFront's origin protocol to HTTPS-only. Then move your prefix list security group rule from port 80 to 443. This encrypts the hop between CloudFront and your origin so the X-Origin-Verify header can't be sniffed in transit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lock down SSM.&lt;/strong&gt; Replace &lt;code&gt;AWS-RunShellScript&lt;/code&gt; with a &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-doc-syntax.html" rel="noopener noreferrer"&gt;custom SSM document&lt;/a&gt; that only runs your deploy script. This limits the blast radius if your GitHub Actions role is ever compromised. See the example earlier in this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security group hygiene.&lt;/strong&gt; Remove SSH (port 22) access entirely — you have SSM Session Manager for shell access. Restrict port 80/443 ingress to the &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/LocationsOfEdgeServers.html" rel="noopener noreferrer"&gt;CloudFront managed prefix list&lt;/a&gt; (&lt;code&gt;com.amazonaws.global.cloudfront.origin-facing&lt;/code&gt;) so only CloudFront can reach your origin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring and detection.&lt;/strong&gt; Set up &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html" rel="noopener noreferrer"&gt;CloudWatch alarms&lt;/a&gt; for CPU, memory, and disk usage. Enable &lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html" rel="noopener noreferrer"&gt;VPC Flow Logs&lt;/a&gt; to capture network traffic metadata. Consider enabling &lt;a href="https://aws.amazon.com/waf/" rel="noopener noreferrer"&gt;AWS WAF&lt;/a&gt; on your CloudFront distribution to filter malicious requests. Without these, a breach or resource issue could go undetected indefinitely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-downtime deploys.&lt;/strong&gt; Set up a blue/green deployment — spin up the new container, health-check it, then swap traffic only if healthy. Your deploy script can pull the new image, start it on a different port, verify it responds, then update nginx and stop the old container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling.&lt;/strong&gt; If you outgrow a single instance, add an &lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/auto-scaling-groups.html" rel="noopener noreferrer"&gt;Auto Scaling Group&lt;/a&gt; with an &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html" rel="noopener noreferrer"&gt;Application Load Balancer&lt;/a&gt; to distribute traffic across multiple instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;If you're done experimenting and want to tear everything down, here's the order I'd go in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Head to Route 53 and delete the A records you created for your domain and &lt;a href="http://www" rel="noopener noreferrer"&gt;www&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Go to CloudFront, disable your distribution, wait about 5 to 10 minutes, then delete it.&lt;/li&gt;
&lt;li&gt;Delete the ACM certificate in Certificate Manager.&lt;/li&gt;
&lt;li&gt;Head to ECR and delete your repository.&lt;/li&gt;
&lt;li&gt;Terminate your EC2 instance and delete the security group and key pair you created.&lt;/li&gt;
&lt;li&gt;Clean up the IAM roles and the OIDC identity provider for GitHub.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Going in this order avoids dependency issues, CloudFront needs to be disabled before you can delete the certificate, and Route 53 records need to be removed before disabling the distribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;Remember the problem we started with? Too many subscriptions, serverless timeouts, and no real control over your stack. We just solved all of that with a single EC2 instance.&lt;/p&gt;

&lt;p&gt;Here's what we built: a t3.micro instance running Ubuntu with nginx as a reverse proxy, Docker for containerization, a CI/CD pipeline with GitHub Actions and ECR, CloudFront as our CDN with SSL via Certificate Manager, and Route 53 pointing our domain to it all. &lt;/p&gt;

&lt;p&gt;This gives you a working foundation you can build on. Check the Production Hardening section above to close the gaps before serving real traffic — but the hard part is done. You own your stack, you understand every piece of it, and you can evolve it on your terms.&lt;/p&gt;

&lt;p&gt;If this helped you out, drop a comment below and let me know what you're deploying. Until next time!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>webhost</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From Chat to Specs: A Deep Dive into AI-Assisted Development with Kiro</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Tue, 15 Jul 2025 16:57:38 +0000</pubDate>
      <link>https://dev.to/kirodotdev/from-chat-to-specs-a-deep-dive-into-ai-assisted-development-with-kiro-1415</link>
      <guid>https://dev.to/kirodotdev/from-chat-to-specs-a-deep-dive-into-ai-assisted-development-with-kiro-1415</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally &lt;a href="https://kiro.dev/blog/from-chat-to-specs-deep-dive/" rel="noopener noreferrer"&gt;published&lt;/a&gt; on the Kiro blog from Ryan Yanchuleff.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As developers, we've all been there. You have a brilliant idea for a feature or application, you fire up your favorite AI coding assistant, and then... you spend the next hour going back and forth, refining requirements, clarifying edge cases, and watching your context window fill up with exploratory conversations before you even write a single line of code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, a new IDE, fundamentally changes how we approach AI-assisted development through Spec-Driven Development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations of current AI coding assistants
&lt;/h2&gt;

&lt;p&gt;Limitations of current AI coding assistants tend to follow a predictable and inefficient pattern. When a developer provides a high-level prompt, the AI immediately jumps into code generation, often before fully understanding the requirements. This premature action leads to a cycle where the developer must repeatedly clarify their intentions with "actually, I meant..." statements, as the initial requirements weren't sufficiently clear. As this exploratory dialogue continues, the context window becomes increasingly cluttered with back-and-forth discussions, leaving limited space for the final code generation. This constrained context space ultimately impacts the quality and completeness of the final output, making the entire process less efficient than it could be. This approach treats the LLM as a code generator first, when it should be considered a thinking partner throughout the entire development lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spec-driven development: Bridging design intent and implementation
&lt;/h2&gt;

&lt;p&gt;If you're working on a challenging feature, Kiro serves as your intelligent sounding board to help you understand your codebase, define your problem clearly, and reach a quality solution efficiently. You can collaborate with Kiro on creating concise specifications that include clear requirements, system architecture, tech stack considerations, and implementation approach. Kiro helps make all requirements and constraints explicit, then uses these specifications as context to complete tasks with fewer iterations and higher accuracy. This is the power of spec-driven development. Let's dive deeper into some of the key benefits of Kiro's approach:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Understand your existing codebase
&lt;/h3&gt;

&lt;p&gt;Before starting new development, Kiro analyzes your existing code and generates three foundational documents: structure.md (codebase architecture), tech.md (technical stack and patterns), and product.md (business context and requirements). This gives you and your team a clear baseline understanding that informs all subsequent specification work. Existing codebases can now take advantage of this new paradigm.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Analyze and plan your project
&lt;/h3&gt;

&lt;p&gt;When you provide a project prompt in spec mode, Kiro's AI doesn't immediately start coding. Instead, it performs deep analysis to understand your requirements, identifies potential challenges, and creates comprehensive planning documents.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Generate comprehensive planning documentation
&lt;/h3&gt;

&lt;p&gt;From a simple prompt, Kiro creates detailed specification files including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requirements Analysis&lt;/strong&gt; - Breaking down your prompt into specific, actionable requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Technical Design&lt;/strong&gt; - Architecture decisions, technology choices, and implementation approach&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Task Breakdown&lt;/strong&gt; - Granular development tasks with clear acceptance criteria&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Collaborate with AI effectively
&lt;/h3&gt;

&lt;p&gt;Kiro saves specification files in your project directory as readable markdown files. You can review, edit, and refine them before any code is written. This creates natural checkpoints for collaboration with team members or stakeholders.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Maximize coding context and efficiency
&lt;/h3&gt;

&lt;p&gt;When it's finally time to write code, Kiro references these specification files rather than cluttering your context window with exploratory conversation. This means maximum context space is available for the actual coding task.&lt;/p&gt;

&lt;h2&gt;
  
  
  The power of spec-driven development
&lt;/h2&gt;

&lt;p&gt;Spec-driven development delivers key advantages that fundamentally improve how teams design, build, and maintain software. Rather than treating planning as overhead, it becomes your competitive advantage. Here's how this approach transforms the development process:&lt;/p&gt;

&lt;h3&gt;
  
  
  Catch problems before they're expensive
&lt;/h3&gt;

&lt;p&gt;Rather than discovering requirements issues mid-development, Kiro identifies and resolves ambiguities upfront. This prevents costly rewrites and provides alignment before coding begins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stay in control of your project's direction
&lt;/h3&gt;

&lt;p&gt;The specification phase creates natural pause points where humans can review, modify, and approve the direction before resources are invested in implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iterate without losing your progress
&lt;/h3&gt;

&lt;p&gt;If you make a mistake in defining your requirements, no problem. You can modify the specification files and regenerate the implementation plan without losing your entire conversation history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep your AI focused on what matters
&lt;/h3&gt;

&lt;p&gt;By externalizing the planning phase to files, Kiro keeps the active context focused on the immediate coding task, leading to higher quality code generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable seamless team collaboration
&lt;/h3&gt;

&lt;p&gt;Specification files serve as living documentation that team members can review, comment on, and contribute to using standard development workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build institutional knowledge
&lt;/h3&gt;

&lt;p&gt;Every decision and requirement is documented, creating a clear audit trail of why certain technical choices were made and preserving context for future team members.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s see Kiro specs in action
&lt;/h2&gt;

&lt;p&gt;The best way to understand spec-driven development is to see it in practice. Whether you're starting fresh or working with an existing codebase, Kiro's systematic approach enables you to build on a solid foundation. Here's how a typical workflow unfolds, from initial concept to implementation-ready specifications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbt4gugjxl718ti2j6q7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbt4gugjxl718ti2j6q7.png" alt="Figure 1: Kiro uses a 'Spec-Driven Development' mode to produce detailed outputs with requirements, designs, and tasks." width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Initiating your project
&lt;/h3&gt;

&lt;p&gt;Before diving into new features, establish context for your project:&lt;/p&gt;

&lt;p&gt;User: "Set up steering for this project"&lt;/p&gt;

&lt;p&gt;Kiro analyzes your existing codebase and generates three foundational documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;structure.md&lt;/strong&gt; - Current architecture, key components, and code organization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tech.md&lt;/strong&gt; - Technology stack, patterns, and technical constraints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;product.md&lt;/strong&gt; - Business context, existing features, and user workflows&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you a clear baseline understanding of what you're building upon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Generating your project specs
&lt;/h3&gt;

&lt;p&gt;Now start laying out the details for the project that you want to build.&lt;/p&gt;

&lt;p&gt;User: "I want to build a task management app for small teams with real-time collaboration features"&lt;/p&gt;

&lt;p&gt;This is where the magic of spec-driven development becomes apparent. Rather than jumping straight into framework selection or database design, Kiro takes a step back to fully understand what you're trying to accomplish. It considers your prompt in the context of the steering documents, identifying how this new feature fits within your existing architecture and constraints.&lt;/p&gt;

&lt;p&gt;Kiro creates a series of documents in sequential order, building from requirements through to actionable tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;requirements.md&lt;/strong&gt; - Detailed feature breakdown including user stories and acceptance criteria&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;design.md&lt;/strong&gt; - Architecture and technology decisions including frameworks, architecture diagrams, and structure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tasks.md&lt;/strong&gt; - Development phases and tasks to be executed in a sequential order&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdf6kbm8hiu9sged5ohjv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdf6kbm8hiu9sged5ohjv.gif" alt="Figure 2: Kiro creates three main documents: requirements, design, and the task list&amp;lt;br&amp;gt;
" width="320" height="180"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Reviewing and refining
&lt;/h3&gt;

&lt;p&gt;You review the specifications, perhaps adding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Additional Requirements
- Integration with Slack for notifications
- Mobile-responsive design priority
- GDPR compliance considerations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Informed Development
&lt;/h3&gt;

&lt;p&gt;Now when Kiro begins coding, it references these comprehensive specifications rather than trying to infer requirements from conversation history. Every implementation decision is grounded in documented requirements and design choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The future of development is here—and it starts in the specs
&lt;/h2&gt;

&lt;p&gt;Spec-driven development represents a shift from reactive coding to proactive specification that isn't just a workflow improvement—it's a fundamental evolution in how we partner with AI to build software. Instead of treating AI as a sophisticated autocomplete tool, spec-driven development positions it as your strategic thinking partner, helping you make better decisions before they become expensive to change. The result? Faster development cycles, higher quality code, fewer surprises, and documentation that actually stays current because it's integral to the process, not an afterthought. The next time you have a feature to build, try leading with specification instead of code. Your future self (and your teammates) will thank you for the clarity, and you might just discover that the best code is the code you plan before you write.&lt;/p&gt;

&lt;p&gt;Ready to experience the difference? Kiro is now available and free during preview with generous usage limits. &lt;a href="https://kiro.dev/downloads/" rel="noopener noreferrer"&gt;Download it and get started today!&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What I Learned from Vibe Coding</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Wed, 26 Mar 2025 21:13:50 +0000</pubDate>
      <link>https://dev.to/erikch/what-i-learned-vibe-coding-30em</link>
      <guid>https://dev.to/erikch/what-i-learned-vibe-coding-30em</guid>
      <description>&lt;p&gt;You may have heard this term called “vibe coding”. It’s a new way of thinking about creating software using AI. It was coined by Andrej Karpathy, and he describes &lt;a href="https://x.com/karpathy/status/1886192184808149383" rel="noopener noreferrer"&gt;vibe coding&lt;/a&gt; as “Giving into the vibes, embrace exponentials, and forget that code even exists”. Karpathy uses an AI coding assistant and he adopts the philosophy of “accepting all”, assuming the AI coding assistant will write and fix the software he’s creating.&lt;/p&gt;

&lt;p&gt;While this way of coding sounds tempting, does it produce accurate enough results given today’s large language model limitations (LLM) and the overall changing landscape? Can you vibe code a complete application without issues? Can you have it create tests, and how does it handle inconsistencies with design? Is this a fad or a real long term strategy developers should learn and adopt? &lt;/p&gt;

&lt;p&gt;I had these same questions too, so I decided to create an experiment for myself. Without any prior knowledge I wanted to update my old, and frankly out of date &lt;a href="https://www.programwitherik.com/" rel="noopener noreferrer"&gt;personal blog&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea8objvczxs4dfqukujc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea8objvczxs4dfqukujc.png" alt="Picture of personal website" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’d vibe my way into a new, and hopefully better looking and functional website. The guard rails I set for myself was that &lt;strong&gt;I could NOT go into the code and make any changes&lt;/strong&gt;. Everything would have to be done by using a prompt and the coding assistant. Additionally, &lt;strong&gt;I would try my best to assume no knowledge of web development&lt;/strong&gt;. &lt;strong&gt;Spoiler&lt;/strong&gt;, I broke the second rule - we’ll chat more about that later. &lt;/p&gt;

&lt;p&gt;If you'd like to follow along, I created a full video of the experience!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/CXCLhA836yo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Let's jump into how to set up this experiment!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure - I'm a Senior Developer Advocate for AWS!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;For this experiment I am using &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line.html?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Amazon Q CLI&lt;/a&gt; as my coding assistant. This command line interface (CLI) tool will be my agent, and I’ll prompt it to create my new personal website. I am a huge fan of CLI tools, and this will work perfectly for my experiment. If you'd like to follow along, Q CLI is &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-installing.html?trk=9ff26abb-282e-49a3-82fe-d185d6344181&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;easy to install&lt;/a&gt; and supports several different operating systems, including my favorites MacOS, WSL and Linux. You can even set it up over SSH if needed. Also, don’t forget to sign up for &lt;a href="https://profile.aws.amazon.com/#/profile/details?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Builder ID&lt;/a&gt;, it’s a free to sign-up and you’ll need it to sign in with the CLI.&lt;/p&gt;

&lt;p&gt;To speed things up a bit, I created a brand new application using Nuxt 3 with Tailwind CSS 4. I followed the procedures on the Nuxt installation &lt;a href="https://nuxt.com/docs/getting-started/installation" rel="noopener noreferrer"&gt;guide&lt;/a&gt; and then chose the Nuxt UI installation that includes &lt;a href="https://tailwindcss.com/docs/installation/using-vite" rel="noopener noreferrer"&gt;Tailwind CSS 4&lt;/a&gt;. I cleared out the starter files and began with a simple index.vue file to start. &lt;/p&gt;

&lt;p&gt;Here is my starting point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fde3re6smiup0yt941ar1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fde3re6smiup0yt941ar1.png" alt="Chrome browser shows hello world" width="800" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s worth mentioning that even though I used Nuxt which uses Vue.js, I’m almost certain this experiment would have worked the same way or even better with React, Angular, or even Svelte. &lt;/p&gt;

&lt;h2&gt;
  
  
  Building My Personal Website
&lt;/h2&gt;

&lt;p&gt;One thing I know about prompting, is that you should try to be specific. So I started with a basic prompt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dv70tjx50gw22hk99ce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dv70tjx50gw22hk99ce.png" alt="personal website prompt" width="800" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This prompt was explicitly written to generate a personal website, with spring colors, and animations. I then listed out that I wanted a blog, contact me, profile picture and bio page. For good measure I added in that I was using Tailwind CSS 4 and it was configured already. Although not necessary I always try to be polite with my prompts, and I was sure to include please and thank you. 😊&lt;/p&gt;

&lt;p&gt;After entering the prompt in and hitting enter I noticed that Q CLI read my directory and summarized what it was going to do. This was nice to see that things were going in the right direction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb672o54dj1o8kx1k639r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb672o54dj1o8kx1k639r.png" alt="summary of what Q CLI is doing" width="800" height="709"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point Q CLI started creating pages and I started blindly accepting ‘y’ for each change. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;It’s worth noting you can set Q CLI to accept all changes by using the /acceptall configuration or -a argument when you begin q chat. However, in this example I decided to just manually accept all changes.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Missing image
&lt;/h3&gt;

&lt;p&gt;After a few times of pressing ‘y’ on my keyboard, and accepting all the changes, I noticed a small issue with the &lt;code&gt;bio.vue&lt;/code&gt; page that was just created. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nycmh78k22bf5a69si4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nycmh78k22bf5a69si4.png" alt="profile placeholder error" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No problem! I told Q to fix the file, and it continued on it’s way!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffv9cmv7au2ese91jizp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffv9cmv7au2ese91jizp.png" alt="Image description" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Nuxt config update
&lt;/h3&gt;

&lt;p&gt;As I was continuing on confirming all changes a message came up to confirm a new update to the &lt;code&gt;nuxt.config.ts&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fej489l830zeyfxeqd402.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fej489l830zeyfxeqd402.png" alt="Update to nuxt config file" width="800" height="812"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of my goals for this experiment was to assume no knowledge of web development and blindly "accept all" to all changes. However, to save time, and to fix an obvious issue, I stopped Q CLI when it tried to make modifications to the &lt;code&gt;nuxt.config.ts&lt;/code&gt; file. One of the primary responsibilities of this file is to add modules. Q CLI wanted to remove my &lt;code&gt;nuxt-ui&lt;/code&gt; module which would have broken all my Tailwind CSS in the app. &lt;/p&gt;

&lt;p&gt;Looking at the output, I believe Q CLI was confused and wanted to add in some PostCSS configuration, which is more often used with Tailwind CSS 3. To be safe here I typed &lt;code&gt;no&lt;/code&gt; here and reminded Q CLI that I was using a module and that I didn’t need this configuration. It continued on without issue, and did not update this file.&lt;/p&gt;

&lt;h3&gt;
  
  
  First iteration of website
&lt;/h3&gt;

&lt;p&gt;After a few more minutes of accepting more changes and fixing the minor mistakes mentioned earlier, my new personal website appeared! 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62rhzmy8udiex8vfb318.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62rhzmy8udiex8vfb318.png" alt="generated personal website" width="800" height="863"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It included most of the features I was expecting including a featured projects section. One thing that was not working was the blog section. So I went back and asked for it to create it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin9nyfvjjfegk8ur21js.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin9nyfvjjfegk8ur21js.png" alt="create blog prompt" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Within a few minutes it added an interactive blog. It even added in pagination, tags, and search functionality!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F458mf1ryo4eoskn5r9gt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F458mf1ryo4eoskn5r9gt.png" alt="blog screenshot" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Overall it did a good job with the blog. It created separate dynamic routes for each page, and the tag filtering and search worked as well. The only concerns I had were with a few small design issues with mobile responsiveness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding more updates
&lt;/h3&gt;

&lt;p&gt;I continued on for another few minutes cleaning up the design. I asked Q CLI to update all the place holder images with images from Unsplash. I then had it clean up a few minor visual issues. This definitely made the site look a little cleaner.&lt;/p&gt;

&lt;p&gt;I then had it commit the changes into it’s own branch using git. It's important having an easy way to roll back or change between commits. As an added bonus from doing this, Q CLI started committing all my changes without me having to ask after every update. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2n4dzwc896s3ssh69ee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2n4dzwc896s3ssh69ee.png" alt="updated website" width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Testing is really important for any software you create. It’s a great way to ensure you don’t break anything while making updates to a code base. When working with AI coding assistants this is even more important. By creating tests AI agents can check that their changes didn’t break anything, and if they do fix the issue.&lt;/p&gt;

&lt;p&gt;To help test this philosophy I asked Q CLI to make some simple tests for each page in the application. With in seconds it installed vitest, and test-utils and began creating tests for each page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcka52hb15t4bfnnuxjc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcka52hb15t4bfnnuxjc.png" alt="testing screenshot" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing surprised me is that after creating all these tests, they didn’t pass. Q CLI went back and made changes 3-4 times until every test passed. I was impressed that it was able to self correct itself without additional prompting. Afterwards I quickly checked the tests out and they looked fine for this project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The goal was to vibe code, and this was a success. I was able to create a profesional, nice looking, personal website that featured a blog, contact page, and bio. I never once had to fix anything in the code myself, and I was able to prompt my way through all the errors and problems that occurred.&lt;/p&gt;

&lt;p&gt;That said, if I hadn't known anything about web development, it would have been a different experience. Q CLI got Tailwind CSS 3 and 4 mixed up and it would have caused an issue if I blindly accepted all the change. On the other hand, I’m confident I could have prompted my way to fix it, it would have just taken longer. This issue may be certainly fixed by the time you read this, but just realize when you are working with the latest libraries, your favorite coding assistant may not be up to date.&lt;/p&gt;

&lt;p&gt;Is vibe coding a fad? I don’t think so. This is a perfectly valid way of creating software, and as LLMs and AI coding assistants get better the number of corrections or errors will decrease. Vibe coding is certainly not a substitute for a real software developer, and I can imagine as you try to vibe code into a much more complicated app, it won’t work as well. You’ll always need to have a developer to help out where the LLM lacks. As for me, I’ll be looking to update my personal site with this new code soon! After I vibe code a few more updates. 😉&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tailwindcss</category>
      <category>genai</category>
      <category>aws</category>
    </item>
    <item>
      <title>How To Create A Fullstack TypeScript App Using AWS Amplify Gen 2</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Fri, 31 May 2024 19:06:37 +0000</pubDate>
      <link>https://dev.to/aws/how-to-create-an-app-on-aws-aws-amplify-gen-2-2534</link>
      <guid>https://dev.to/aws/how-to-create-an-app-on-aws-aws-amplify-gen-2-2534</guid>
      <description>&lt;p&gt;&lt;a href="https://docs.amplify.aws/" rel="noopener noreferrer"&gt;AWS Amplify Gen 2 &lt;/a&gt;is a brand new way to create fullstack applications on AWS. In this tutorial, we’ll explore how to get started creating a fullstack TypeScript application with Next.js! We’ll add storage and then connect it to our Amplify connected UI component library to upload files.&lt;/p&gt;

&lt;p&gt;With  Gen 2 we focused our efforts on creating a great developer experience. We wanted to make sure that you could focus on your frontend code instead of worrying about setting up your backend infrastructure. In this new version we created a brand new TypeScript safe DX that supports fast local development, and is backed by AWS Cloud Development Kit (CDK).&lt;/p&gt;

&lt;p&gt;We recently released Gen 2 for general availability. Feel free to let us know what you think and leave a comment below.&lt;/p&gt;

&lt;p&gt;With that said, before you get started makes sure your familiar with TypeScript, VSCode and Git. You'll also need a GitHub account and make sure Git is installed locally on the command line. We'll be using Next.js, however you don't need to be an expert in React to try out this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are We Building Today?
&lt;/h2&gt;

&lt;p&gt;We will be creating an app that allows you to create todos with pictures. You’ll be using the &lt;a href="https://github.com/aws-samples/amplify-next-template" rel="noopener noreferrer"&gt;Amplify Next Starter&lt;/a&gt; template and have it deployed to the AWS Amplify console. We'll create and connect to AWS AppSync, our managed GraphQL service, and use &lt;a href="https://aws.amazon.com/pm/cognito" rel="noopener noreferrer"&gt;Amazon Cognito&lt;/a&gt;, our customer identity and access management service, to authenticate and authorize users. Additionally, we'll use S3 for file storage of our images.&lt;/p&gt;

&lt;p&gt;In this tutorial I'll walk you through step-by-step on how to create a new AWS Amplify Gen 2 app using the console, cloning that app, opening it in VSCode and adding in some additional features. We'll then deploy our changes back to the Amplify Gen 2 console to our hosted environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;We need to first have an AWS account ready. If you are not signed up, try out the &lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;AWS Free Tier&lt;/a&gt;! This will give you plenty of free resources to try out AWS and many of it's services. Keep in mind, all the services we'll work with today are on-demand and you'll only get charged when you are using them. &lt;/p&gt;

&lt;p&gt;We will be using the Next.js Amplify &lt;a href="https://github.com/new?template_name=amplify-next-template&amp;amp;template_owner=aws-samples&amp;amp;name=amplify-next-template&amp;amp;description=My%20Amplify%20Gen%202%20starter%20application" rel="noopener noreferrer"&gt;starter template&lt;/a&gt; as described in the &lt;a href="https://docs.amplify.aws/react/start/quickstart/" rel="noopener noreferrer"&gt;Quickstart guide&lt;/a&gt;. Clone the repository using the Amplify Next.js Template into your own Github account to get started. &lt;/p&gt;

&lt;p&gt;Afterwords, sign into the &lt;a href="https://signin.aws.amazon.com/signin" rel="noopener noreferrer"&gt;AWS management console&lt;/a&gt;. Search at the top for AWS Amplify, and click it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6wdqz81rm6okvxkckhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6wdqz81rm6okvxkckhu.png" alt="AWS console choosing Amplify button" width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Amplify Console choose Create new app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq38cj20yqzoisaiyr6j9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq38cj20yqzoisaiyr6j9.png" alt="AWS console clicking create new app button" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select GitHub as the Git provider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt3abc1jiwtvt5r745ya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt3abc1jiwtvt5r745ya.png" alt="AWS console selecting the GitHub provider button" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next page you'll see a popup asking to connect to your Github account. This will give Amplify access to your account so it can connect to the Next.js repo you just created in the previous step from the starter template.&lt;/p&gt;

&lt;p&gt;Select your app from the App name field and click next. If you don’t see your app click the &lt;code&gt;Update Github permissions&lt;/code&gt; button and re-authorize with Github. On the next page leave all the default settings and click next. On the last page click &lt;code&gt;Save and deploy&lt;/code&gt;. Amplify will now begin to deploy your new app!&lt;/p&gt;

&lt;p&gt;The deployment may take several minutes. It will host your Next.js frontend and create infrastructure for your Amazon Cognito and AWS AppSync service backend. We’ll discuss this more later, and how we can make updates and changes to it. Until then, let's jump into VSCode and setup the frontend!&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating the frontend
&lt;/h2&gt;

&lt;p&gt;Let's take a look at our app, and make some updates.&lt;/p&gt;

&lt;p&gt;Open up your favorite terminal and clone the Github repo you just created in the previous section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone &amp;lt;The_Copied_Githhub_URL&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open up VSCode to that cloned project and open a terminal to the project’s root folder. Run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @aws-amplify/ui-react-storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install the &lt;a href="https://ui.docs.amplify.aws" rel="noopener noreferrer"&gt;Amplify UI component library&lt;/a&gt;. We'll use this later to add a file uploader, called Storage Manager, to our frontend to help with uploading pictures. &lt;/p&gt;

&lt;p&gt;If you look at the app folder structure you'll notice an amplify folder. This folder houses all the resources and files that will help us connect to our backend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1ng7hn1yxa8f8f0f4tk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1ng7hn1yxa8f8f0f4tk.png" alt="List of folders for amplify" width="754" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By convention, the starter template will create an Amazon Cognito instance, and configure it for email login. The configuration for this will be in the resources file at &lt;code&gt;amplify/auth/resource.ts&lt;/code&gt;. For more information on the auth resource please checkout the &lt;a href="https://docs.amplify.aws/react/build-a-backend/auth/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It also creates an AWS Appsync instance with a &lt;code&gt;Todo&lt;/code&gt; schema in the &lt;code&gt;amplify/data/resource.ts&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;data/resource.ts&lt;/code&gt; to match the code below. The only difference is we are adding a new key to the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// amplify/data/resource.ts&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publicApiKey&lt;/span&gt;&lt;span class="p"&gt;()]),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ClientSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authorizationModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;defaultAuthorizationMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apiKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiKeyAuthorizationMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;expiresInDays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This schema does a few things.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adds a new Todo model with all the resolvers needed CRUDL (Create, Read, Update, Delete, List) and connects it to a new DynamoDB instance.&lt;/li&gt;
&lt;li&gt;The Todo model will have a content and a key  fields - both are strings.&lt;/li&gt;
&lt;li&gt;Adds public authorization rules so all users can create, read, update and delete, as long as they have the public api key.&lt;/li&gt;
&lt;li&gt;Sets a default authorization mode of apiKey and sets the expiration for 30 days. This is the public key.&lt;/li&gt;
&lt;li&gt;Exports a Schema that can be used by the frontend to ensure type safety end-to-end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This resource data pattern is very powerful by allowing the user to completely configure the backend data schema all in code using TypeScript.&lt;/p&gt;

&lt;p&gt;For the sake of this tutorial we will leave the default resource file unchanged for auth in the &lt;code&gt;amplify/auth/resources.ts&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Storage
&lt;/h2&gt;

&lt;p&gt;We need to be able to upload images and retrieve them. We can do this using the Amplify Storage Category. &lt;/p&gt;

&lt;p&gt;Create a new folder at &lt;code&gt;amplify/storage&lt;/code&gt;. Inside this folder create a &lt;code&gt;resource.ts&lt;/code&gt; file. Copy the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// amplify/storage/resource.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/backend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineStorage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todosStorage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;media/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;guest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;write&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="c1"&gt;// additional actions such as "write" and "delete" can be specified depending on your use case&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a public bucket for users to upload our images too. Users will be able to upload images to the &lt;code&gt;media&lt;/code&gt; folder and they’ll have access to &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, and &lt;code&gt;delete&lt;/code&gt; them. &lt;/p&gt;

&lt;p&gt;Next, let’s add this storage resource to our &lt;code&gt;backend.ts&lt;/code&gt; file. Open the &lt;code&gt;backend.ts&lt;/code&gt; file and add a new &lt;code&gt;storage&lt;/code&gt; import. The complete file is below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//amplify/backend.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineBackend&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/backend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./auth/resource.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./data/resource.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./storage/resource.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;defineBackend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All our backend resource should be ready to go!&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting the Sandbox
&lt;/h3&gt;

&lt;p&gt;To test locally we'll create a new ephemeral environment using Amplify tooling.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you've never setup AWS on the command line you'll need to run a few commands here to make sure your environment can connect to your account. Please follow the &lt;a href="https://docs.amplify.aws/gen2/start/account-setup/" rel="noopener noreferrer"&gt;Set up your AWS Account section&lt;/a&gt; section in our docs before continuing on.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ampx sandbox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create an environment in AWS based on the resources you configured in your &lt;code&gt;amplify&lt;/code&gt; folder. In this case it will create a Amazon Cognito, S3 Storage and AWS AppSync service. At any time you can stop this command, and it will delete all the resources it just created.&lt;/p&gt;

&lt;p&gt;It will also create an &lt;code&gt;amplify_outputs.json&lt;/code&gt; file in the root of your app. We'll need this file in the next section to setup our client &lt;code&gt;aws-amplify&lt;/code&gt; library so it can talk to the backend services.&lt;/p&gt;

&lt;p&gt;This will take a few minutes to run, after it completes continue onto the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update page.tsx
&lt;/h2&gt;

&lt;p&gt;The starter template has a built in todos app in it. Let’s modify the &lt;code&gt;page.tsx&lt;/code&gt; file so we can upload files and connect them with our todos.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;page.tsx&lt;/code&gt; app add an import for &lt;code&gt;StorageManager&lt;/code&gt;, &lt;code&gt;StorageImage&lt;/code&gt; and to our Amplify UI components library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/page.tsx&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StorageImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StorageManager&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react-storage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI components, &lt;code&gt;Card&lt;/code&gt;, &lt;code&gt;Flex&lt;/code&gt;, &lt;code&gt;Text&lt;/code&gt; and &lt;code&gt;Button&lt;/code&gt; are used to style our app. The &lt;code&gt;StorageImage&lt;/code&gt; and &lt;code&gt;StorageManager&lt;/code&gt; will allow us to add images and show them.&lt;/p&gt;

&lt;p&gt;We need to be able to delete todos. Add the following function under the &lt;code&gt;listTodos()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/page.tsx&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;
 &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deleteTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll need to update the &lt;code&gt;createTodo&lt;/code&gt; function. It will now take in two arguments, &lt;code&gt;key&lt;/code&gt; and &lt;code&gt;content&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/page.tsx&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll now do a few updates to the return statement. We’ll add a new &lt;code&gt;StorageImage&lt;/code&gt; that will display our images. We’ll also add in the &lt;code&gt;StorageManager&lt;/code&gt;. This will display a component so we can upload images.  &lt;/p&gt;

&lt;p&gt;Update the return so it matches below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/page.tsx&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;My&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;deleteTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Flex&lt;/span&gt; &lt;span class="nx"&gt;justifyContent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-between&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StorageImage&lt;/span&gt;
                  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Flex&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;))}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ul&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StorageManager&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;media/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;acceptedFileTypes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
        &lt;span class="nx"&gt;maxFileCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onUploadStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Todos content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
          &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="nx"&gt;variation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;elevated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Card&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="nc"&gt;FilePicker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;variation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;Choose&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt; &lt;span class="nx"&gt;For&lt;/span&gt; &lt;span class="nx"&gt;Upload&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may notice some props on the &lt;code&gt;StorageManager&lt;/code&gt;. The components prop can be used to completely override the look and feel of the component. In this case we changed the container slightly to add a card around it. We also changed the &lt;code&gt;FilePicker&lt;/code&gt; so we can use a different kind of button to upload.&lt;/p&gt;

&lt;p&gt;We added an event listener called &lt;code&gt;onUploadStart&lt;/code&gt;. This will trigger whenever an upload begins. It will first ask the user for the todos content. It will then call the &lt;code&gt;createTodo&lt;/code&gt; which will create the todo in the database.&lt;/p&gt;

&lt;p&gt;Here is the complete &lt;code&gt;app/page.tsx&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/amplify/data/resource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-amplify/data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StorageImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StorageManager&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react-storage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Amplify&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-amplify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;outputs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/amplify_outputs.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react/styles.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;Amplify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generateClient&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;listTodos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observeQuery&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTodos&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deleteTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;listTodos&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;My&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;deleteTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Flex&lt;/span&gt; &lt;span class="nx"&gt;justifyContent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-between&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StorageImage&lt;/span&gt;
                  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Flex&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;))}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ul&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StorageManager&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;media/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;acceptedFileTypes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
        &lt;span class="nx"&gt;maxFileCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onUploadStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Todo content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nf"&gt;createTodo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
          &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="nx"&gt;variation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;elevated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Card&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="nc"&gt;FilePicker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;variation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;Choose&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt; &lt;span class="nx"&gt;For&lt;/span&gt; &lt;span class="nx"&gt;Upload&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trying it out
&lt;/h2&gt;

&lt;p&gt;Go ahead and start the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The front page will load. Go ahead and click the &lt;code&gt;Add Todo&lt;/code&gt; and &lt;code&gt;Choose File for Upload button&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1ki6ngzgswqy4htej60.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1ki6ngzgswqy4htej60.png" alt="Storage Manager displayed with upload button" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose a todo name and then choose a file. You’ll see the upload occur and a new todo displayed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc22f3z7xoisymgdyo01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frc22f3z7xoisymgdyo01.png" alt="Storage Manager with one amplify.png file uploaded" width="800" height="1007"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can go ahead and add a few more. You can also click any todo for them to delete.&lt;/p&gt;

&lt;p&gt;Good job! You've created a full stack app!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy to Production
&lt;/h2&gt;

&lt;p&gt;In the first steps we created a new Next.js app using a starter template, we then cloned it down to edit it locally. We can now commit our changes and have it deployed back to our AWS Gen 2 console that will trigger a new branch build .&lt;/p&gt;

&lt;p&gt;If you like, you can go ahead and stop the sandbox environment. Just go to the terminal where it's running and hit Ctrl/Cmd C. If for some reason you accidentally closed the terminal, you can always stop any sandbox environments by going back to the Amplify Gen 2 console and choosing the &lt;code&gt;Manage Sandboxes&lt;/code&gt; button in the &lt;code&gt;All apps&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;The sandbox environments are for testing only, and can be started or stopped at any time. The production environments are attached to your git branches that you pushed to Github. In this case the main branch is the production environment. If we choose to do so, we can create multiple environments if needed based on any branch we create and connect them to the AWS Gen 2 console.&lt;/p&gt;

&lt;p&gt;To deploy our changes with the new frontend updates, all we'll need to do is a git commit and push. Open the terminal in your project and paste the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Updated todos App"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will push all our changes to the Amplify Gen 2 console and trigger another build. After a few minutes click on the domain listed in the Amplify console to see it in action!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial we learned how to get started with AWS Amplify Gen 2. We used the Amplify Next.js starter template to create a new Amplify hosted app with our backend. We then made a todo app with photo storage, used the sandbox environment to test, and then pushed it to production to see the changes!&lt;/p&gt;

&lt;p&gt;To clean up this environment, make sure to stop the sandbox environment and then go back into the amplify console, click on our app, then go to &lt;code&gt;App settings → General Settings&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffl58t1jeaz5wswod88ys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffl58t1jeaz5wswod88ys.png" alt="App settings drop down" width="564" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the &lt;code&gt;Delete app&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6tmm3zbepieery2qgix0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6tmm3zbepieery2qgix0.png" alt="Delete app button" width="276" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to learn more make sure to check our the &lt;a href="https://docs.amplify.aws" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>amplify</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Five Tutorials To Create Your Fullstack Apps Using AWS Amplify Gen 2</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Fri, 01 Mar 2024 18:16:59 +0000</pubDate>
      <link>https://dev.to/aws/five-tutorials-to-create-your-fullstack-apps-using-aws-amplify-gen-2-29n3</link>
      <guid>https://dev.to/aws/five-tutorials-to-create-your-fullstack-apps-using-aws-amplify-gen-2-29n3</guid>
      <description>&lt;p&gt;&lt;a href="https://docs.amplify.aws/gen2/" rel="noopener noreferrer"&gt;AWS Amplify Gen 2&lt;/a&gt; is the next generation of tooling from Amplify! It’s a new code-first developer experience that helps frontend developers create fullstack applications on AWS. You can now author your frontend and backend definition completely in TypeScript, create ephemeral sandbox environments to test against, and deploy to production using Git branch-based environments.&lt;/p&gt;

&lt;p&gt;Since we’ve entered into developer preview at the end of last year a lot of content has been created! Let’s jump in!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2x2r4d9qrtv1tu5qpsnm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2x2r4d9qrtv1tu5qpsnm.png" alt="Tweet About Amplify Gen 2" width="800" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://aws.amazon.com/blogs/mobile/introducing-amplify-gen2/" rel="noopener noreferrer"&gt;Introducing The Next Generation of AWS Amplify’s Fullstack Development Experience&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Amplify has unveiled a code-first way to develop fullstack applications. This post deep dives into why we decided to go this direction and how to get started. If you are looking for a overview of Gen 2, here is the place!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://dev.to/aws-builders/the-guide-to-set-up-nextjs-authentication-and-data-fetching-with-aws-amplify-gen2-2blf"&gt;The guide to set up NextJS Authentication and data fetching with AWS Amplify Gen2&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This post describes how to add auth and data to your application using Amplify Gen 2. This step-by-step tutorial covers creating a new Next.js 14 application, adding in Gen 2, and setting up authentication. It also shows how to use middleware to protect routes and use the generateClient API to add and list data from the backend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnoyn4x4muw7k3tsu46j2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnoyn4x4muw7k3tsu46j2.png" alt="Tweet about Amplify Gen 2" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?si=I70cAcds7lO0ZJTr&amp;amp;v=wcSMnICY-_8&amp;amp;feature=youtu.be" rel="noopener noreferrer"&gt;How To Create A Full Stack Typesafe App With No Knowledge! Using AWS Gen2 and Next.js 14!&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This video describes how to create a fullstack application using Gen 2 with Next.js 14. In it, Erik creates a protected Next.js 14 application using auth. The application helps you store titles and allows multiple users to leave comments. Viewers will learn how to create, update, list, and delete comments and titles, as well as how to setup dynamic routes. All of this backed by Gen 2, and AWS AppSync.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://aws.amazon.com/blogs/mobile/the-future-of-web-development-aws-amplifys-code-first-approach/" rel="noopener noreferrer"&gt;The future of web development: AWS Amplify’s Code First Approach&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This extended article describes what Amplify Gen 2 is, the problems frontend developers face with fullstack applications, and how to resolve them. Christian uses several code examples to illustrate how to use Gen 2, extend its functionality and how to use it in your next project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomvztkmqgjqsvq5od1us.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomvztkmqgjqsvq5od1us.png" alt="Tweet about Amplify Gen 2" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?si=JhoSMVEhrQuOU58l&amp;amp;v=MOeL0cKy7-Q&amp;amp;feature=youtu.be" rel="noopener noreferrer"&gt;How To Add A Social Login To Your Website Using AWS!&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This advanced tutorial describes how to extend Amplify Gen 2’s functionality to add a Github social login. It deep dives into OAuth, OIDC, authorization and authentication.  It describes how AWS Amplify Cognito, as well as identity providers can work together with Gen 2 to create social logins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There you have it! Five different tutorials on how to use AWS Amplify Gen 2! Please leave a comment below if you’ve tried Gen 2! Thanks&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>aws</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Secrets of Styling Forms (Using AWS Amplify Studio)</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Mon, 17 Jul 2023 21:37:19 +0000</pubDate>
      <link>https://dev.to/aws/secrets-of-styling-forms-using-aws-amplify-studio-49f0</link>
      <guid>https://dev.to/aws/secrets-of-styling-forms-using-aws-amplify-studio-49f0</guid>
      <description>&lt;p&gt;Forms are deceptively difficult. When you create a form for your website you need to make a lot of decisions. You have to worry about validation, structure, and how to send the data to your backend. You need to figure out how to handle errors, and how to style and configure your form. You’ll need to make sure your forms are accessible. With this in mind, &lt;a href="https://docs.amplify.aws/console/formbuilder/overview/" rel="noopener noreferrer"&gt;AWS Amplify Studio&lt;/a&gt; with its form builder can help!&lt;/p&gt;

&lt;p&gt;In this post I’ll discuss how to add a new form using Amplify Studio that is connected to an &lt;a href="https://docs.amplify.aws/console/data/data-model/" rel="noopener noreferrer"&gt;AWS Appsync data source&lt;/a&gt;. We’ll look at how to customize that form by adding in a dark/light mode. We’ll look at how we can further customize the validation rules and edit the error messages! We’ll then test our submissions by displaying the results using Amplify’s JavaScript GraphQL API library.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you'd like to see the completed code. Please check out this &lt;a href="https://github.com/ErikCH/StyleFormsExampleAmplify" rel="noopener noreferrer"&gt;repo&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;We’ll create a basic Todo form using Amplify studio. &lt;/p&gt;

&lt;p&gt;To begin, sign into your &lt;a href="https://aws.amazon.com/console/" rel="noopener noreferrer"&gt;AWS Console&lt;/a&gt; and search for AWS Amplify and click on it. If this is the first time using Amplify, choose &lt;code&gt;Get Started&lt;/code&gt; and click on the &lt;code&gt;Get started&lt;/code&gt; again under Amplify Studio. If you’ve already created an Amplify app in this region you can click &lt;code&gt;New app&lt;/code&gt; → &lt;code&gt;Build an app&lt;/code&gt;. Choose an app name, click &lt;code&gt;Confirm deployment&lt;/code&gt;. Afterwords click &lt;code&gt;Launch Studio&lt;/code&gt; to begin!&lt;/p&gt;

&lt;p&gt;If you are an existing Amplify Studio user, Launch Studio from your existing application. This feature is available to all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3a3iibkrd5932dvqcuts.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3a3iibkrd5932dvqcuts.png" alt="Launch Studio screenshot" width="500" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Data
&lt;/h2&gt;

&lt;p&gt;Inside Amplify Studio let’s create a data source that we can use with our form. Click on &lt;code&gt;Data&lt;/code&gt; in the menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6b9xtvbyb1ggrihlbnop.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6b9xtvbyb1ggrihlbnop.png" alt="Data Tab" width="322" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Add model&lt;/code&gt; and create Todo. Add the completed and the title fields.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8v6ccnyi3dfxczp1qp8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8v6ccnyi3dfxczp1qp8.png" alt="Data modeling picture" width="800" height="857"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After adding these models make sure you click the &lt;code&gt;Save and Deploy&lt;/code&gt; button in the top right hand corner. It may take a few minutes to deploy.&lt;/p&gt;

&lt;p&gt;After the model is deployed you’ll see a page with some instructions to pull the latest client config into your React application. Make sure to copy the pull command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gs8mb24qbyb2rm5b4n9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gs8mb24qbyb2rm5b4n9.png" alt="Pull config instructions" width="800" height="893"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js App Setup
&lt;/h2&gt;

&lt;p&gt;Now that we have our data and forms in place we can pull the information into our existing application. I’ll assume that you’ve already created a new Next.js application. If not, and you are starting a new Next.js application, make sure the &lt;a href="https://ui.docs.amplify.aws/react/getting-started/usage/nextjs" rel="noopener noreferrer"&gt;App router turned off&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Open up your app and install these libraries with your favorite package manager.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @aws-amplify/ui-react aws-amplify react-icons
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If not already installed, make sure you also install the Amplify CLI globally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @aws-amplify/cli &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this is the first time using Amplify in a while, make sure you upgrade to the latest version of the CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;amplify upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, take the command you copied from the last section and paste it into your terminal running it in the root of your Next.js project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;amplify pull &lt;span class="nt"&gt;--appId&lt;/span&gt; &amp;lt;replace-this-with-your-id&amp;gt; &lt;span class="nt"&gt;--envName&lt;/span&gt; &amp;lt;replace-with-env-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see a few new files and directories in your project. &lt;/p&gt;

&lt;p&gt;Since we are dealing with AppSync, you’ll need to generate some GraphQL query files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;amplify codegen add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate some helpful GraphQL query/subscription/mutation files that we’ll use later on in this tutorial.&lt;/p&gt;

&lt;p&gt;Let’s setup Amplify so we can use our new forms!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: If you are using Next.js with the app router or using Vite, you may need some small additional configuration. Please follow the &lt;a href="https://ui.docs.amplify.aws/react/getting-started/usage/nextjs" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; or &lt;a href="https://ui.docs.amplify.aws/react/getting-started/usage/vite" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; usage guide.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Our Form and Customizing it
&lt;/h2&gt;

&lt;p&gt;Let’s begin setting up our Next application so it can talk to our AppSync backend.&lt;/p&gt;

&lt;p&gt;Inside your main entry point file in your application, add these lines to configure Amplify with the correct styles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Amplify&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-amplify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;awsExports&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../aws-exports&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;Amplify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;awsExports&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react/styles.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we’ll be adding in a special theme file. This file is used to customize global themes throughout your application. In our case we will us to to override the default theme of our forms to support a &lt;code&gt;dark&lt;/code&gt; mode. The &lt;code&gt;defaultDarkModeOverride&lt;/code&gt; will add this for us.&lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;theme.tsx&lt;/code&gt; in your &lt;code&gt;src&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultDarkModeOverride&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;defaultDarkModeOverride&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we’ll add this Theme provider to a new Layout file. In the &lt;code&gt;src/components&lt;/code&gt; folder create a new &lt;code&gt;Layout.tsx&lt;/code&gt; file. Inside this file we’ll add a new &lt;code&gt;colorMode&lt;/code&gt; useState variable that we’ll use to change the color of the theme. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ThemeProvider&lt;/code&gt; surrounds our application and provides theming capabilities with design tokens to our app. These tokens can completely override the look and feel of our application. The official &lt;a href="https://ui.docs.amplify.aws/react/theming/theme-provider" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; discuss this in more detail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ColorMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ThemeProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setColorMode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ColorMode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;colorMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s add in a toggle button so users can select between dark and light mode. We’ll be using the react-icons/md  set for these icons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MdOutlineDarkMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MdOutlineLightMode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-icons/md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ColorMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ThemeProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ToggleButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ToggleButtonGroup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setColorMode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ColorMode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;colorMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt; &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"column"&lt;/span&gt; &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100vw"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100vh"&lt;/span&gt; &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"background.primary"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToggleButtonGroup&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt;
            &lt;span class="na"&gt;isExclusive&lt;/span&gt;
            &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setColorMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ColorMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToggleButton&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"light"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MdOutlineLightMode&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ToggleButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToggleButton&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MdOutlineDarkMode&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ToggleButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ToggleButtonGroup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ToggleButtonGroup&lt;/code&gt; is from a set of UI primitives offered by the &lt;code&gt;@aws-amplify/ui-react&lt;/code&gt; library. It creates a nice looking toggle button. On every click of the toggle button the &lt;code&gt;colorMode&lt;/code&gt; is set, and is updated in the &lt;code&gt;ThemeProvider&lt;/code&gt;. This will cause the screen to change between dark and light mode.&lt;/p&gt;

&lt;p&gt;Finally, we’ll add in an &lt;code&gt;AppHeader&lt;/code&gt; component to help sticky this layout to the top left hand corner. We’ll also add a nice icon using the &lt;code&gt;Icon&lt;/code&gt; component primitive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MdOutlineDarkMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MdOutlineLightMode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-icons/md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ColorMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ThemeProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ToggleButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ToggleButtonGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AppHeader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;
      &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt;
      &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;
      &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-between"&lt;/span&gt;
      &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"center"&lt;/span&gt;
      &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1rem"&lt;/span&gt;
      &lt;span class="na"&gt;boxShadow&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt;
      &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sticky"&lt;/span&gt;
      &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
      &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;
      &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"background.primary"&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PropsWithChildren&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setColorMode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ColorMode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;colorMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;
        &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"column"&lt;/span&gt;
        &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100vw"&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100vh"&lt;/span&gt;
        &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"background.primary"&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AppHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Icon&lt;/span&gt;
            &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"brand.primary.60"&lt;/span&gt;
            &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"xl"&lt;/span&gt;
            &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M10.8484 4.19838C10.7939 4.2926 10.7939 4.40867 10.8484 4.50288L21.3585 22.6711C21.413 22.7653 21.5138 22.8233 21.6228 22.8233H23.9901C24.225 22.8233 24.3718 22.5696 24.2543 22.3666L12.5605 2.15225C12.4431 1.94925 12.1495 1.94925 12.0321 2.15225L10.8484 4.19838Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M15.2084 22.6711C15.2629 22.7653 15.3636 22.8233 15.4726 22.8233H17.8461C18.081 22.8233 18.2278 22.5696 18.1104 22.3666L9.48857 7.46259C9.37113 7.25959 9.07755 7.25959 8.96011 7.46259C6.09213 12.4203 3.21732 17.4003 0.336955 22.3816C0.219574 22.5846 0.366371 22.8383 0.601212 22.8383H11.7185C11.9533 22.8383 12.1001 22.5846 11.9827 22.3816L10.8455 20.4158C10.791 20.3216 10.6903 20.2635 10.5813 20.2635H4.8952C4.77776 20.2635 4.70437 20.1367 4.76308 20.0352L9.0912 12.5534C9.14991 12.4519 9.29671 12.4519 9.35542 12.5534L15.2084 22.6711Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToggleButtonGroup&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colorMode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt;
            &lt;span class="na"&gt;isExclusive&lt;/span&gt;
            &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setColorMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ColorMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToggleButton&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"light"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MdOutlineLightMode&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ToggleButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToggleButton&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MdOutlineDarkMode&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ToggleButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ToggleButtonGroup&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AppHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll need to update the &lt;code&gt;_app.tsx&lt;/code&gt; file so our new layout surrounds the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;AppProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Toggle the dark mode and see it in action.&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzqb6a2lgk8z9clzbav0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzqb6a2lgk8z9clzbav0.png" alt="Header with Light Mode" width="800" height="96"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1trbf909ldzsfglul9vb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1trbf909ldzsfglul9vb.png" alt="Header with dark mode" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding our form and customizing them
&lt;/h2&gt;

&lt;p&gt;Let’s now add in our form to the main route &lt;code&gt;index.tsx&lt;/code&gt; file in our Next.js application.&lt;/p&gt;

&lt;p&gt;Import the form onto the page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TodoForm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/ui-components/TodoCreateForm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TodoForm&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xbvfe9we4w6kcbe8teh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xbvfe9we4w6kcbe8teh.png" alt="Todo form with title clear and submit" width="556" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By convention this form is already connected to the AppSync model we created earlier. Under the covers it uses something called &lt;a href="https://docs.amplify.aws/lib/datastore/getting-started/q/platform/js/" rel="noopener noreferrer"&gt;DataStore&lt;/a&gt; to sync and send data to the backend. &lt;/p&gt;

&lt;p&gt;We can customize this form to our liking. Let’s update the validation rule first. Assume that we don’t want users to enter in empty spaces. We’ll add a check for that and display a custom error message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TodoForm&lt;/span&gt;
    &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
    &lt;span class="na"&gt;onValidate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
             &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
             &lt;span class="na"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please enter a value!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="p"&gt;};&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;validateResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="p"&gt;},&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this validation in place, users won’t be able to submit a blank input in without getting an error. The forms logic will handle the rest and will disable the input as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekdyk7gicy13kyvafj3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekdyk7gicy13kyvafj3w.png" alt="Todo form showing error- please enter value" width="494" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine we want to show a message every time the user successfully submit a todo. The generated forms have event handlers that you can hook into and listen for. In this example, we’ll create a &lt;code&gt;showSuccess&lt;/code&gt; variable that we can use to trigger an alert message, that fades after two seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;showSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setShowSuccess&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TodoForm&lt;/span&gt;
          &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="nx"&gt;onValidate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please enter a value!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;validateResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;}}&lt;/span&gt;
          &lt;span class="nx"&gt;onSuccess&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setShowSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nf"&gt;setShowSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showSuccess&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Alert&lt;/span&gt; &lt;span class="na"&gt;variation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Todo added!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;onSuccess&lt;/code&gt; handler will trigger as soon as the form has submitted successfully. We can then show an alert! This &lt;code&gt;Alert&lt;/code&gt; is another &lt;code&gt;@aws-amplify/ui-react&lt;/code&gt; UI primitive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzrgl9fe12vpfhjzyzdg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzrgl9fe12vpfhjzyzdg.png" alt="Todo form showing alert message Todo added" width="500" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have a way to add todo’s, we will need a way to show them.&lt;/p&gt;

&lt;p&gt;Let’s add a method that grabs all the todos. This will use the Amplify JS library GraphQL API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;graphqlOperation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-amplify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GraphQLQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GraphQLSubscription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ListTodosQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnCreateTodoSubscription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;listTodos&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/graphql/queries&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTodo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GraphQLQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ListTodosQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listTodos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allTodos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;listTodos&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;_deleted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;setTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filteredTodos&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we created our application using Studio and form builder, by convention our forms uses &lt;a href="https://docs.amplify.aws/lib/datastore/getting-started/q/platform/js/" rel="noopener noreferrer"&gt;DataStore&lt;/a&gt; and &lt;code&gt;conflict resolution&lt;/code&gt; turned on in the background. Conflict resolution creates a versioned data source that enhances the object data model with metadata. At this time, you cannot turn this off when using Form Builder and studio. &lt;/p&gt;

&lt;p&gt;What that means is that our Data model has a few extra meta fields including  &lt;code&gt;_deleted&lt;/code&gt; and  &lt;code&gt;_version&lt;/code&gt; . These are automatically added to our data model when the model was created. When you delete a record, it doesn’t get deleted, the way you normally may expect. Instead, the &lt;code&gt;_deleted&lt;/code&gt; field gets set to &lt;code&gt;true&lt;/code&gt; and the record remains. Keep this in mind when dealing with data with &lt;code&gt;conflict resolution&lt;/code&gt; turned on. This is why we must filter out records with &lt;code&gt;_deleted&lt;/code&gt; set to true in our code, that way we don’t accidentally show deleted data.&lt;/p&gt;

&lt;p&gt;It’s good to know that if you decide to make updates or deletions to any record using the Amplify GraphQL API, you must also include the &lt;code&gt;_version&lt;/code&gt; in the GraphQL input since conflict resolution is turned on. The &lt;code&gt;_version&lt;/code&gt; field will increment on every change that occurs to the record. You’ll need to keep track of this &lt;code&gt;_version&lt;/code&gt; field when you make updates or delete an item.&lt;/p&gt;

&lt;p&gt;To guarantee the correct order, we sort by the &lt;code&gt;createdAt&lt;/code&gt; time.&lt;/p&gt;

&lt;p&gt;Now that we have a way to grab data, we’ll create a GraphQL subscription that will load as soon as the application loads using a &lt;code&gt;useEffect&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;graphqlOperation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-amplify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GraphQLQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GraphQLSubscription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ListTodosQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnCreateTodoSubscription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onCreateTodo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/graphql/subscriptions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTodo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getTodos&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GraphQLSubscription&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OnCreateTodoSubscription&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;graphqlOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onCreateTodo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setTodo&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prevValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;onCreateTodo&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first trigger the &lt;code&gt;getTodos&lt;/code&gt; and then run our subscription. As soon as a new Todo is created, the subscription is triggered. We can then use our &lt;code&gt;setTodo&lt;/code&gt; to save the data into the array. Alternatively, we could call &lt;code&gt;getTodos&lt;/code&gt; again on every subscription change, however for the sake of this post we’ll manipulate the &lt;code&gt;todos&lt;/code&gt; array and add the new todo to it instead. &lt;/p&gt;

&lt;p&gt;Let’s put this all together into one file. I’ve added a few more UI primitives to make things easier including a &lt;code&gt;Collection&lt;/code&gt; component that is used with the array to display data in a nice row.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Heading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/ui-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TodoForm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/ui-components/TodoCreateForm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;graphqlOperation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-amplify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GraphQLQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GraphQLSubscription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-amplify/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ListTodosQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnCreateTodoSubscription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;listTodos&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/graphql/queries&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onCreateTodo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/graphql/subscriptions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;showSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setShowSuccess&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTodo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GraphQLQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ListTodosQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listTodos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allTodos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;listTodos&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;_deleted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;setTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filteredTodos&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getTodos&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GraphQLSubscription&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OnCreateTodoSubscription&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;graphqlOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onCreateTodo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setTodo&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prevValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;onCreateTodo&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTheme&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;
      &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;
      &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"stretch"&lt;/span&gt;
      &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;
        &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"column"&lt;/span&gt;
        &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"medium"&lt;/span&gt;
        &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"xxl"&lt;/span&gt;
        &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"background.primary"&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;New ToDo&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TodoForm&lt;/span&gt;
          &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
          &lt;span class="na"&gt;onValidate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please enter a value!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;validateResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setShowSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nf"&gt;setShowSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;showSuccess&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Alert&lt;/span&gt; &lt;span class="na"&gt;variation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Todo added!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;
        &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"column"&lt;/span&gt;
        &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;
        &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"xxl"&lt;/span&gt;
        &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"background.secondary"&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Heading&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;List of Todos&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Heading&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Collection&lt;/span&gt; &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"list"&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt;
              &lt;span class="na"&gt;variation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"elevated"&lt;/span&gt;
              &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;marginTop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1rem"&lt;/span&gt;
              &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1rem"&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;textDecoration&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;line-through&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Flex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s take a look at it in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpu340tlxotfr7bb2z28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpu340tlxotfr7bb2z28.png" alt="Full todo form with list of todos" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And with dark mode, after clicking the dark mode icon!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjcsa0sa89lzgdu3f382.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjcsa0sa89lzgdu3f382.png" alt="Full todo form with list of todos in dark mode" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a lot more we could do here. We could add a way to take already existing todos and delete them and mark them complete. I’ll leave that exercise for you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deletion
&lt;/h2&gt;

&lt;p&gt;Once you’re done with your application you can delete all your resources by running amplify delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blog post we’ve learned about Amplify Studio forms, how to customize them and how to add additional validation error messages. If you’d like to learn more about Amplify in general please check out the &lt;a href="https://docs.amplify.aws/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;. You can also check the &lt;a href="https://docs.amplify.aws/console/tutorial/buildui/" rel="noopener noreferrer"&gt;Amplify Studio tutorial&lt;/a&gt; for more information on how to use it.&lt;/p&gt;

&lt;p&gt;Please let me know if you have any questions, I’m at &lt;a href="https://twitter.com/erikch" rel="noopener noreferrer"&gt;ErikCH on Twitter&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>aws</category>
      <category>amplify</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Front End Flash from AWS Amplify</title>
      <dc:creator>Erik Hanchett</dc:creator>
      <pubDate>Wed, 24 May 2023 15:31:28 +0000</pubDate>
      <link>https://dev.to/aws/front-end-flash-from-aws-amplify-4jah</link>
      <guid>https://dev.to/aws/front-end-flash-from-aws-amplify-4jah</guid>
      <description>&lt;p&gt;Hello and welcome to the &lt;a href="https://aws.amazon.com/amplify/" rel="noopener noreferrer"&gt;AWS Amplify&lt;/a&gt; community roundup post!&lt;/p&gt;

&lt;p&gt;Amplify is a set of tools for frontend and mobile developers to build, ship, and host full-stack applications on AWS.&lt;/p&gt;

&lt;p&gt;This post is meant to be a place where you can find the latest information on Amplify, AppSync - our fully managed GraphQL service, and all things frontend and mobile!  We’ll be showcasing our latest tutorials, guides, conference talks and some future plans we have in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Post?
&lt;/h2&gt;

&lt;p&gt;Many internal and external teams at Amplify create great content. From our developer advocate team to marketing, docs, solution architects and our community members. This content has an amazing amount of insight that I'd like to share with all of you!&lt;/p&gt;

&lt;p&gt;This post will help highlight that content, and hopefully help educate you on what we offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback
&lt;/h2&gt;

&lt;p&gt;If you have a minute please complete this &lt;a href="https://pulse.buildon.aws/survey/PMYYIAUU" rel="noopener noreferrer"&gt;short survey&lt;/a&gt;. This will really help us make this post better!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating SaaS Applications With AppSync and Amplify!
&lt;/h2&gt;

&lt;p&gt;Have you ever imagined creating a software as a service (Saas) startup? Did you know Amplify has all the tools you need to get started? Check out this tweet from Ryan!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F740u7agx9unmuvo58fb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F740u7agx9unmuvo58fb6.png" alt=" " width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s really nice to see real people using AWS Amplify to solve their problems. I really liked how Ryan used Amplify and the AWS Free Tier with AWS Startups to get thousands of free credits! &lt;/p&gt;

&lt;p&gt;Michael Liendo talked about this very subject recently during the AWS Developer Innovation Day!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twitch.tv/videos/1804180223" rel="noopener noreferrer"&gt;Build a full-stack, fully typed app with GraphQL and AWS AppSync (fast forward to 01:26:50)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure you watch some of the other talks too, you’ll get a good idea of many of the innovations that AWS provides.&lt;/p&gt;

&lt;p&gt;He also open source his contributions so you can take a look at his code on Github!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/focusOtter/microsaas-backend" rel="noopener noreferrer"&gt;MicroSaas Backend&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Modern Apps With Amplify
&lt;/h2&gt;

&lt;p&gt;The non-stop innovations in the web space amazes me. Every month there is a new library or framework that I must try out! Here at AWS we want to meet developers where their at. We want you to build on modern tooling and frameworks and not worry about the infrastructure. Here are some tutorials and guides that follow these principles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Astro, SolidJS and Data With Amplify
&lt;/h3&gt;

&lt;p&gt;Have you tried out Astro or SolidJS? Christian Nwamba recently published two excellent tutorials on how to deploy a SolidJS and Astro Blog to Amplify hosting!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/BzF-IlzWn0o" rel="noopener noreferrer"&gt;Deploy a SolidJS App With AWS Amplify&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
and&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/EPsF6SM5Nlw" rel="noopener noreferrer"&gt;Deploy an Astro Blog With AWS Amplify&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
Christian also created an excellent video on how to work with data APIs, and how to get the correct types from the backend to the frontend using AWS AppSync.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/Mg8Mqb0AFlQ" rel="noopener noreferrer"&gt;Better Developer Experience When Working With Data APIs&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign In With Google With Amplify Authenticator
&lt;/h3&gt;

&lt;p&gt;A very common use case that we often see in modern web development is logging in with Google. This allows customers with a Google account to login with a few clicks.&lt;/p&gt;

&lt;p&gt;Amplify supports this feature, and in the following shorts Erik Hanchett shows how you can add this to your website in minutes!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/shorts/21PL0d-XlAM" rel="noopener noreferrer"&gt;Add A Sign In With Google To Your Website Part 1&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/shorts/iTP32OIo1Ak" rel="noopener noreferrer"&gt;Add A Sign In With Google To Your Website Part 2&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
Erik has also been busy live streaming building a full-stack app! He’s been using a modern stack including, Amplify services, Next.js, AppSync, lambdas and more! You can find him live streaming weekly on his YouTube channel on Fridays, or you can watch his last two videos on demand!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=0gfg8zM5OWk" rel="noopener noreferrer"&gt;Building a full-stack app with Authenticator Live streamed April 6&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/live/NXCSZyPczvw?feature=share" rel="noopener noreferrer"&gt;Building a full-stack app with Authenticator Live streamed April 14&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Flutter and Cross Platform Development
&lt;/h3&gt;

&lt;p&gt;Did you know we have a &lt;a href="https://docs.amplify.aws/lib/q/platform/flutter/" rel="noopener noreferrer"&gt;Flutter library&lt;/a&gt; that works directly with Amplify services? And we just recently updated our docs site with instructions on how to create a &lt;a href="https://docs.amplify.aws/start/q/integration/flutter/" rel="noopener noreferrer"&gt;budget tracking app&lt;/a&gt;? &lt;/p&gt;

&lt;p&gt;In this video Muhammed Salih Güler talks about cross platform development, how how the Amplify Flutter library works! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=eiJFPSNoH_U" rel="noopener noreferrer"&gt;CB Community Flutter&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
Salih has also been busy live streaming! Check out his last live stream on using the Flame Engine with Flutter!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=P-kd44htOyY" rel="noopener noreferrer"&gt;Flame Engine Live Stream Livestream April 6&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage Manager and Liveness
&lt;/h3&gt;

&lt;p&gt;In April we launched our new &lt;a href="https://ui.docs.amplify.aws/react/connected-components/storage/storagemanager" rel="noopener noreferrer"&gt;Storage Manger UI&lt;/a&gt; and &lt;a href="https://ui.docs.amplify.aws/react/connected-components/liveness" rel="noopener noreferrer"&gt;Face Liveness&lt;/a&gt; components!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87urwt2l3zx09nf4fmt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87urwt2l3zx09nf4fmt7.png" alt=" " width="800" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Liveness component helps deter fraud by using our Amazon Rekognition service. You may want to use a service like this for an ID verification system or for fraud detection. This is built into our React, Flutter, Android and Swift libraries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk6trbezbqbsq7ydjk5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk6trbezbqbsq7ydjk5a.png" alt=" " width="800" height="923"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Amplify UI StorageManger has been released for general availability! This file uploader makes it straight forward to upload files directly to S3, with only a few lines of boilerplate code! &lt;/p&gt;

&lt;h2&gt;
  
  
  Where in the World is Amplify ✈️ ?
&lt;/h2&gt;

&lt;p&gt;The developer advocate team has been busy traveling!  &lt;/p&gt;

&lt;p&gt;Muhammed Salih Güler will be giving a talk on June 2 at Flutter Connection in Paris! Stop on by and find Salih so you can learn about Amplify and Flutter! He'll also be at the &lt;a href="https://aws.amazon.com/events/summits/amsterdam/" rel="noopener noreferrer"&gt;AWS Summit in Amsterdam&lt;/a&gt; on June 1st and Flutter Berlin on June 7th.&lt;/p&gt;

&lt;p&gt;From May 31 - June 2, Michael Liendo and Erik Hanchett are giving talks at &lt;a href="https://www.renderatl.com/" rel="noopener noreferrer"&gt;RenderATL&lt;/a&gt; in Atlanta! Amplify will be sponsoring the conference so please look for our AWS booth and say hi if you’re at the conference&lt;/p&gt;

&lt;p&gt;Erik will also be giving a talk on May 24 - 26 - at &lt;a href="https://us.vuejs.org/" rel="noopener noreferrer"&gt;VueConf US&lt;/a&gt; 2023 in New Orleans! Erik will have some stickers on him, so feel free to stop him and grab some.&lt;/p&gt;

&lt;p&gt;If you’re a part of the &lt;a href="https://aws.amazon.com/developer/community/community-builders/" rel="noopener noreferrer"&gt;community builders&lt;/a&gt; program with AWS, you can also here Michael Liendo give a talk on MicroSaaS architecture on May 24th! We are always looking for more community developers so feel free to signup and you might be chosen!&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;As you can see we’ve had a busy few months! What content would you like to see? Leave a comment or tweet me at &lt;a href="https://twitter.com/ErikCH" rel="noopener noreferrer"&gt;@ErikCH&lt;/a&gt;! Thanks&lt;/p&gt;

</description>
      <category>aws</category>
      <category>amplify</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
