<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Education on Luke Little</title><link>https://lukelittle.com/categories/education/</link><description>Recent content in Education on Luke Little</description><image><title>Luke Little</title><url>https://lukelittle.com/images/og-default.png</url><link>https://lukelittle.com/images/og-default.png</link></image><generator>Hugo -- 0.152.2</generator><language>en</language><lastBuildDate>Sun, 15 Feb 2026 09:00:00 -0500</lastBuildDate><atom:link href="https://lukelittle.com/categories/education/index.xml" rel="self" type="application/rss+xml"/><item><title>Building Your Personal Brand: A Student's Guide to Online Presence</title><link>https://lukelittle.com/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/</link><pubDate>Sun, 15 Feb 2026 09:00:00 -0500</pubDate><guid>https://lukelittle.com/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/</guid><description>Build a professional online presence in 10 minutes with the student-branding-starter template: a Hugo site on GitHub Pages for your projects.</description><content:encoded><![CDATA[<p>What does your online presence say about you?</p>
<p>For many students, searching their name online brings up little more than a LinkedIn profile and perhaps some social media accounts. This limited visibility can make it challenging to stand out professionally or showcase your actual skills and projects.</p>
<p>A personal website offers a solution - providing a dedicated space where you can control your professional narrative and present your work on your own terms.</p>
<p>Despite the benefits, many students don&rsquo;t create personal websites due to perceived barriers:</p>
<ul>
<li>Thinking web development skills are required</li>
<li>Believing they need extensive projects to showcase first</li>
<li>Assuming it&rsquo;s expensive or time-consuming</li>
</ul>
<p>The <a href="https://github.com/lukelittle/student-branding-starter">student-branding-starter</a> template addresses these concerns by providing a ready-to-use Hugo website template designed specifically for students. It can be deployed to GitHub Pages in minutes at no cost, comes pre-configured, and makes maintaining your online presence as simple as writing Markdown files.</p>
<p>This template isn&rsquo;t just a technical project - it&rsquo;s a foundation for your professional identity online. It creates a platform that can grow with you as you add projects, document your learning, and develop your portfolio.</p>
<p>In this article, I&rsquo;ll cover what the student-branding-starter template offers, how to set it up in under 10 minutes, and how you can use it to create an effective online presence.</p>
<h2 id="why-this-matters">Why This Matters</h2>
<p>Having a personal website can significantly boost your professional presence as a student. Here&rsquo;s why it&rsquo;s worth considering:</p>
<h3 id="professional-presence">Professional Presence</h3>
<p>Your online presence matters in today&rsquo;s job market. When someone searches your name online, having a personal website gives you control over what they find. It supplements your LinkedIn profile and provides a more comprehensive view of your skills and interests.</p>
<p>A personal website demonstrates initiative - it shows you&rsquo;re willing to put in effort to present yourself professionally, which is itself a quality employers value.</p>
<h3 id="practical-demonstration-of-skills">Practical Demonstration of Skills</h3>
<p>Resumes are full of claims about technical skills, but a personal website lets you demonstrate those skills with actual examples. Instead of simply listing &ldquo;Python&rdquo; as a skill, you can show a Python project you built, explain your approach to solving problems, and showcase your coding style.</p>
<p>This transforms abstract claims into concrete evidence of your abilities. It&rsquo;s the difference between telling someone you can bake and showing them a cake you made.</p>
<h3 id="building-a-portfolio-over-time">Building a Portfolio Over Time</h3>
<p>One significant advantage of a personal website is that it grows with you. Each project, article, or tutorial you add becomes part of your professional story.</p>
<p>Content you create can serve multiple purposes:</p>
<ul>
<li>Showcase your technical skills</li>
<li>Demonstrate your communication abilities</li>
<li>Document your progress as a developer</li>
<li>Help you remember solutions to problems you&rsquo;ve solved</li>
<li>Potentially help others facing similar challenges</li>
</ul>
<p>As you add more content over time, the value of your site increases. By graduation, you&rsquo;ll have built a comprehensive portfolio that tells your story better than a resume ever could.</p>
<h3 id="accessibility-to-employers">Accessibility to Employers</h3>
<p>A personal site makes your work accessible to anyone interested in your skills - including potential employers. When applying for positions, you can include your website URL on your resume, LinkedIn profile, and in your email signature.</p>
<p>This gives reviewers a way to learn more about you beyond your formal application materials and may help you stand out in a competitive field.</p>
<h3 id="cost-effective-solution">Cost-Effective Solution</h3>
<p>The student-branding-starter template deploys to GitHub Pages, which hosts static websites completely free. There&rsquo;s no monthly subscription or hidden fees.</p>
<p>Most other portfolio platforms either charge recurring fees or place your content behind their branding. With this approach, you invest only time, not money - and you maintain complete ownership of your content.</p>
<h2 id="what-youre-building">What You&rsquo;re Building</h2>
<p><img src="/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/building-your-personal-brand-student-guide_hu_3409ebc3f59eee88.webp" srcset="/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/building-your-personal-brand-student-guide_hu_e18e3409c804d23d.webp 750w, /posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/building-your-personal-brand-student-guide_hu_3409ebc3f59eee88.webp 1500w" sizes="(max-width: 800px) 100vw, 750px"
       width="1500" height="854" alt="Student Branding Starter - Sample Portfolio Website" loading="lazy" decoding="async"></p>
<p>Let&rsquo;s talk about what you&rsquo;re actually getting with the student-branding-starter template.</p>
<p>I built this after seeing students struggle with complex web development stacks just to put up a simple portfolio. We don&rsquo;t need React or a database for a personal site – simpler is often better.</p>
<p>The template creates a modern, professional website built on proven technology that just works.</p>
<h3 id="the-tech-behind-it">The Tech Behind It</h3>
<p>The workflow is intentionally simple:</p>
<p>You write content in Markdown files, push your changes to GitHub, and GitHub Actions automatically runs Hugo to build your site. The built site is deployed to GitHub Pages, making your content live on the web with global distribution.</p>
<p>Hugo generates your site – it&rsquo;s blazing fast and converts simple text files to a beautiful website. GitHub handles the version control and hosting, while GitHub Actions automates the deployment process.</p>
<p>This means your workflow is dead simple. You write content in Markdown (basically plain text with some formatting). When you save and push your changes, everything rebuilds itself automatically. Your site stays live at yourusername.github.io/repository-name.</p>
<p>No databases to configure. No servers to maintain. No complicated deployment pipelines. Just write content, push, and your site updates.</p>
<h3 id="what-comes-pre-built">What Comes Pre-Built</h3>
<p>I&rsquo;ve included everything students need right out of the box:</p>
<p>A blog section for writing about your projects, what you&rsquo;re learning, or technical concepts you want to explain. A dedicated projects page that showcases your work in a portfolio format. An about page where you tell your story and highlight your skills – often the second most visited page after your homepage.</p>
<p>There&rsquo;s also a &ldquo;Now&rdquo; page showing what you&rsquo;re currently focused on (inspired by the /now movement), and a contact page for networking opportunities.</p>
<p>The design is responsive and works beautifully on phones, tablets, and desktops. It&rsquo;s SEO-optimized so Google can find and index your content. It includes both dark and light modes for better reading experience. And it even has multilingual support with English and Spanish included by default.</p>
<p>Social sharing integration helps your content spread, and the whole thing loads lightning fast, scoring 90+ on Google PageSpeed metrics.</p>
<p>I&rsquo;ve made it opinionated enough to look professional immediately – no design skills needed – but flexible enough that you can make it your own without getting overwhelmed by options.</p>
<h2 id="the-10-minute-setup">The 10-Minute Setup</h2>
<p>The student-branding-starter template is designed for quick setup. Here&rsquo;s how to get your site up and running in about 10 minutes.</p>
<h3 id="step-1-fork-the-repository-2-minutes">Step 1: Fork the Repository (2 minutes)</h3>
<p>First, we need to make your own copy of the template.</p>
<p>Head over to github.com/lukelittle/student-branding-starter and look for the &ldquo;Fork&rdquo; button in the top-right corner. Click it and select your GitHub account as the destination.</p>
<p>If you want an especially clean URL, you can rename the repository to yourusername.github.io during this process, but that&rsquo;s optional.</p>
<p>Once you click &ldquo;Create Fork,&rdquo; GitHub will take about 30 seconds to copy everything to your account.</p>
<p><img src="/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/forking-the-repository_hu_d4aa18bd797527dc.webp" srcset="/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/forking-the-repository_hu_d5faf1d79ae4f7c6.webp 750w, /posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/forking-the-repository_hu_d4aa18bd797527dc.webp 1500w" sizes="(max-width: 800px) 100vw, 750px"
       width="1500" height="851" alt="Forking the repository" loading="lazy" decoding="async"></p>
<p>Forking is better than starting from scratch because you get a direct copy of the repository that you can modify however you want. All the GitHub Actions workflows come pre-configured, and you maintain a connection to the original repo in case you want updates later.</p>
<h3 id="choosing-your-version">Choosing Your Version</h3>
<p>The student-branding-starter has two versions maintained in separate Git branches:</p>
<ul>
<li><strong><code>main</code> branch</strong> (default): English-only</li>
<li><strong><code>multilingual</code> branch</strong>: English + Spanish bilingual support</li>
</ul>
<p><strong>To use the multilingual version</strong>:</p>
<ol>
<li><strong>When forking</strong>: Before clicking &ldquo;Create Fork&rdquo;, use the branch dropdown to select <code>multilingual</code> instead of <code>main</code></li>
<li><strong>Switching an existing fork</strong>: Clone your repository and run:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git checkout multilingual
</span></span><span style="display:flex;"><span>git push origin multilingual --set-upstream
</span></span></code></pre></div></li>
</ol>
<p>Note: Deployment works identically via GitHub Actions regardless of which branch you choose.</p>
<blockquote>
<p><strong>Personal note</strong>: My blog is currently English-only, though I plan to eventually make it bilingual (English/Spanish). Since I learned computer science in English, writing technical content in Spanish is something I&rsquo;m still developing.</p>
</blockquote>
<h3 id="step-2-enable-github-pages-1-minute">Step 2: Enable GitHub Pages (1 minute)</h3>
<p>Now we need to turn on the free hosting.</p>
<p>Go to your newly forked repository and click the Settings tab. In the left sidebar, find and click &ldquo;Pages.&rdquo; Under the &ldquo;Build and deployment&rdquo; section, select &ldquo;GitHub Actions&rdquo; as your source, then save.</p>
<p><img src="/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/enabling-github-pages_hu_349016f3edb70eb7.webp" srcset="/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/enabling-github-pages_hu_4dba01e0a198b6f9.webp 750w, /posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/enabling-github-pages_hu_349016f3edb70eb7.webp 1500w" sizes="(max-width: 800px) 100vw, 750px"
       width="1500" height="853" alt="Enabling GitHub Pages" loading="lazy" decoding="async"></p>
<p>This small step tells GitHub to use the workflow file that&rsquo;s already included in the template. This file handles building your site with Hugo and deploying it to GitHub Pages automatically whenever you make changes.</p>
<h3 id="step-3-make-it-yours-5-minutes">Step 3: Make It Yours (5 minutes)</h3>
<p>Time to personalize your site. We&rsquo;ll edit the main configuration file directly on GitHub for now.</p>
<p>Find and click on the hugo.toml file in your repository, then hit the pencil icon to edit it. This file controls how your site looks and functions.</p>
<p>You&rsquo;ll want to update a few key sections:</p>
<p>First, change the baseURL to match your GitHub username and repository name:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">baseURL</span> = <span style="color:#e6db74">&#34;https://yourusername.github.io/repository-name/&#34;</span>
</span></span></code></pre></div><p>Next, replace the title and author information with your name:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">title</span> = <span style="color:#e6db74">&#34;Your Name - Personal Site&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">author</span> = <span style="color:#e6db74">&#34;Your Name&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">description</span> = <span style="color:#e6db74">&#34;Student, developer, and lifelong learner.&#34;</span>
</span></span></code></pre></div><p>Then update your social links so visitors can connect with you:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[[<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">socialIcons</span>]]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span> = <span style="color:#e6db74">&#34;github&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">url</span> = <span style="color:#e6db74">&#34;https://github.com/yourusername&#34;</span>
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>[[<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">socialIcons</span>]]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span> = <span style="color:#e6db74">&#34;linkedin&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">url</span> = <span style="color:#e6db74">&#34;https://linkedin.com/in/yourprofile&#34;</span>
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>[[<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">socialIcons</span>]]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span> = <span style="color:#e6db74">&#34;email&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">url</span> = <span style="color:#e6db74">&#34;mailto:your.email@example.com&#34;</span>
</span></span></code></pre></div><p>Finally, personalize your home page information:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">homeInfoParams</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Title</span> = <span style="color:#e6db74">&#34;Hi, I&#39;m Your Name 👋&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Content</span> = <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">I&#39;m studying computer science at State University with a focus on machine learning.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">I write about my projects and what I&#39;m learning here.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">**Currently:** Building a recommendation engine for my senior project.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span></code></pre></div><p>When you&rsquo;re finished, scroll down and click &ldquo;Commit changes,&rdquo; add a message like &ldquo;Customize site with my information,&rdquo; and commit again.</p>
<p>This commit triggers your first build and deployment automatically.</p>
<h3 id="step-4-watch-the-magic-happen-1-minute">Step 4: Watch the Magic Happen (1 minute)</h3>
<p>Click over to the &ldquo;Actions&rdquo; tab in your repository. You&rsquo;ll see a workflow running – this is GitHub building your site.</p>
<p>Wait for the green checkmark to appear, indicating that deployment was successful. This usually takes just 1-2 minutes.</p>
<p><img src="/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/github-actions-deployment_hu_8f4587ff10d68980.webp" srcset="/posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/github-actions-deployment_hu_b5fd55612dcba61c.webp 750w, /posts/2026/02/building-your-personal-brand-a-students-guide-to-online-presence/github-actions-deployment_hu_8f4587ff10d68980.webp 1500w" sizes="(max-width: 800px) 100vw, 750px"
       width="1500" height="852" alt="GitHub Actions deployment" loading="lazy" decoding="async"></p>
<h3 id="step-5-see-your-live-site-1-minute">Step 5: See Your Live Site (1 minute)</h3>
<p>Head back to Settings → Pages. You&rsquo;ll see a message with your site URL: &ldquo;Your site is live at <a href="https://yourusername.github.io/repository-name/%22">https://yourusername.github.io/repository-name/&quot;</a></p>
<p>Click that link, and boom – your professional personal site is live on the internet.</p>
<p>That&rsquo;s it. In less than 10 minutes, you&rsquo;ve gone from nothing to having a fully functional personal website. No web development experience required.</p>
<h2 id="writing-your-first-content">Writing Your First Content</h2>
<p>Once your site is live, the next step is filling it with content that demonstrates your skills and interests.</p>
<h3 id="creating-a-blog-post">Creating a Blog Post</h3>
<p>Blog posts are an excellent way to document your learning and showcase your problem-solving approach. Consider writing about:</p>
<ul>
<li>Projects you&rsquo;re working on</li>
<li>Concepts you&rsquo;ve recently learned</li>
<li>Technical challenges you&rsquo;ve overcome</li>
<li>Tools or libraries you&rsquo;ve explored</li>
</ul>
<p>To create a new post, you&rsquo;ll use Hugo&rsquo;s command line interface:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Clone your repository locally</span>
</span></span><span style="display:flex;"><span>git clone https://github.com/yourusername/repository-name.git
</span></span><span style="display:flex;"><span>cd repository-name
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Install Hugo if you haven&#39;t already (macOS example)</span>
</span></span><span style="display:flex;"><span>brew install hugo
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Create a new post</span>
</span></span><span style="display:flex;"><span>hugo new posts/my-first-post.md
</span></span></code></pre></div><p>This creates a file at <code>content/posts/my-first-post.md</code> with default front matter:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>title: &#34;My First Post&#34;           # Post title
</span></span><span style="display:flex;"><span>date: 2026-02-15                 # Publication date
</span></span><span style="display:flex;"><span>draft: true                      # Draft status (true = hidden)
</span></span><span style="display:flex;"><span>tags: []                         # Categories for organizing
</span></span><span style="display:flex;"><span>summary: &#34;&#34;                      # Brief description
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Your content goes here...
</span></span></code></pre></div><p>The front matter (section between <code>---</code> lines) contains metadata about your post:</p>
<ul>
<li><strong>title:</strong> The main headline</li>
<li><strong>date:</strong> Publication date</li>
<li><strong>draft:</strong> Set to <code>false</code> when ready to publish</li>
<li><strong>tags:</strong> Categories for organization</li>
<li><strong>summary:</strong> Brief preview shown in listings</li>
</ul>
<p>Below the front matter, you write your content using Markdown, which is a simple formatting syntax. A typical post might include headings, code blocks, lists, and images:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#75715e">## Project Overview
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>This post documents my process building a simple data visualization tool.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">### The Problem
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>I needed to visualize temperature data collected from multiple sensors.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">### The Solution
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>I built a visualization tool using Python with Matplotlib:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">```python
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"></span><span style="color:#f92672">import</span> matplotlib.pyplot <span style="color:#66d9ef">as</span> plt
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> pandas <span style="color:#66d9ef">as</span> pd
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Load the data</span>
</span></span><span style="display:flex;"><span>data <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>read_csv(<span style="color:#e6db74">&#39;temperature_data.csv&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Create the visualization</span>
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>figure(figsize<span style="color:#f92672">=</span>(<span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">6</span>))
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>plot(data[<span style="color:#e6db74">&#39;timestamp&#39;</span>], data[<span style="color:#e6db74">&#39;temperature&#39;</span>])
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>title(<span style="color:#e6db74">&#39;Temperature Readings Over Time&#39;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>xlabel(<span style="color:#e6db74">&#39;Time&#39;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>ylabel(<span style="color:#e6db74">&#39;Temperature (°C)&#39;</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>grid(<span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>plt<span style="color:#f92672">.</span>show()
</span></span><span style="display:flex;"><span><span style="color:#e6db74">```</span>
</span></span></code></pre></div><p>When your post is ready to publish:</p>
<ol>
<li>Set <code>draft: false</code> in the front matter</li>
<li>Commit and push to your repository:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git add content/posts/my-first-post.md
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#e6db74">&#34;Add my first blog post&#34;</span>
</span></span><span style="display:flex;"><span>git push
</span></span></code></pre></div><p>GitHub Actions will automatically rebuild and deploy your site with the new content.</p>
<h3 id="creating-project-pages">Creating Project Pages</h3>
<p>The Projects section of your site is designed specifically for showcasing your portfolio work. While blog posts can explain concepts or document your learning journey, project pages follow a more structured format to highlight specific work you&rsquo;ve completed.</p>
<p>To create a new project page:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>hugo new projects/my-project.md
</span></span></code></pre></div><p>This creates a file with project-specific front matter:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>title: &#34;My Project&#34;
</span></span><span style="display:flex;"><span>date: 2026-02-15
</span></span><span style="display:flex;"><span>draft: true
</span></span><span style="display:flex;"><span>tags: [&#34;python&#34;, &#34;web-dev&#34;]
</span></span><span style="display:flex;"><span>summary: &#34;A brief description of this project&#34;
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Overview
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>A short introduction to what this project is and why you built it.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="font-weight:bold">**Live Demo:**</span> [Link to demo if available]
</span></span><span style="display:flex;"><span><span style="font-weight:bold">**GitHub:**</span> [Link to repository]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Problem Statement
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>What problem were you trying to solve?
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Tech Stack
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Technology 1
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Technology 2
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Technology 3
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Key Features
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Feature 1: Brief description
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Feature 2: Brief description
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Feature 3: Brief description
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## What I Learned
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>What skills or knowledge did you gain from this project?
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Challenges
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>What obstacles did you encounter and how did you overcome them?
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Results
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>What was the outcome of the project? Include metrics if possible.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Future Improvements
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>How would you extend or improve this project given more time?
</span></span></code></pre></div><p>The template prompts you to include important aspects of your project:</p>
<ul>
<li>What problem it solves</li>
<li>Technologies used</li>
<li>Key features implemented</li>
<li>Learning outcomes</li>
<li>Challenges encountered</li>
<li>Results achieved</li>
<li>Future development plans</li>
</ul>
<p>This structure helps present your work in a comprehensive way that demonstrates both technical abilities and your problem-solving approach.</p>
<p>When documenting projects, remember that even smaller projects can be valuable portfolio pieces when you explain your process and what you learned. Class assignments, hackathon projects, and personal explorations all deserve documentation.</p>
<p>Once you&rsquo;ve completed your project page, set <code>draft: false</code> in the front matter, commit and push to make it live.</p>
<h3 id="updating-your-about-page">Updating Your About Page</h3>
<p>The About page tells visitors who you are, what motivates you, and what skills you bring. Edit <code>content/about.md</code> with:</p>
<ul>
<li>Your educational background</li>
<li>Technical skills and strengths</li>
<li>Career interests and goals</li>
<li>Personal motivations or philosophy</li>
<li>Links to your resume or key projects</li>
</ul>
<p>This page is often the second most-visited after your homepage, so invest time in making it authentic and engaging.</p>
<h2 id="making-it-yours">Making It Yours</h2>
<p>Now that you have the basics working, let&rsquo;s customize your site to truly make it your own.</p>
<h3 id="adding-your-profile-photo">Adding Your Profile Photo</h3>
<ol>
<li>Add your photo to the <code>static/images/</code> directory</li>
<li>Edit <code>hugo.toml</code> to reference it:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">profileMode</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">profilePicture</span> = <span style="color:#e6db74">&#34;/images/your-photo.jpg&#34;</span>
</span></span></code></pre></div><p>Keep your photo professional but authentic. A simple headshot with good lighting works best.</p>
<h3 id="customizing-colors-and-style">Customizing Colors and Style</h3>
<p>The template uses PaperMod theme, which offers built-in customization:</p>
<ol>
<li>Create a file at <code>assets/css/extended/custom.css</code></li>
<li>Add your custom CSS:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>:<span style="color:#a6e22e">root</span> {
</span></span><span style="display:flex;"><span>  --primary-color: <span style="color:#ae81ff">#4a89dc</span>;
</span></span><span style="display:flex;"><span>  --border-radius: <span style="color:#ae81ff">8</span><span style="color:#66d9ef">px</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>.<span style="color:#a6e22e">post-title</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">font-size</span>: <span style="color:#ae81ff">2.5</span><span style="color:#66d9ef">rem</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This overrides the default styles without modifying theme files.</p>
<h3 id="setting-up-a-custom-domain-optional">Setting Up a Custom Domain (Optional)</h3>
<p>While <code>yourusername.github.io/repository-name</code> works well, a custom domain like <code>yourname.com</code> looks more professional:</p>
<ol>
<li>Purchase a domain from Namecheap, Cloudflare, Porkbun, or similar</li>
<li>In your repository, go to <strong>Settings → Pages</strong></li>
<li>Under &ldquo;Custom domain&rdquo;, enter your domain name</li>
<li>Set up DNS records as instructed</li>
<li>Check &ldquo;Enforce HTTPS&rdquo;</li>
<li>Update <code>hugo.toml</code> with your new domain:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#a6e22e">baseURL</span> = <span style="color:#e6db74">&#34;https://yourname.com/&#34;</span>
</span></span></code></pre></div><p>For detailed instructions, see the <a href="https://github.com/lukelittle/student-branding-starter#custom-domain-setup">Custom Domain section</a> in the repository README.</p>
<h3 id="adding-analytics-optional">Adding Analytics (Optional)</h3>
<p>To track visitors and popular content:</p>
<ol>
<li>Create a Google Analytics account</li>
<li>Get your measurement ID (G-XXXXXXXX)</li>
<li>Add to <code>hugo.toml</code>:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">analytics</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">google</span> = {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">SiteVerificationTag</span> = <span style="color:#e6db74">&#34;G-XXXXXXXX&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span></code></pre></div><p>This gives you insights into which content resonates with visitors.</p>
<h2 id="content-strategy-for-students">Content Strategy for Students</h2>
<p>A common question is: &ldquo;What should I write about?&rdquo; Here are some content ideas that work well for student portfolio sites:</p>
<h3 id="content-ideas">Content Ideas</h3>
<p><strong>1. Class Project Documentation</strong></p>
<p>When documenting class projects, focus on your unique perspective:</p>
<ul>
<li>Design decisions you made and why</li>
<li>What you might do differently in future iterations</li>
<li>Any extensions or features you added beyond requirements</li>
<li>Specific challenges you encountered and how you solved them</li>
</ul>
<p><strong>2. Learning Reflections</strong></p>
<p>Consider writing about concepts you&rsquo;ve recently mastered:</p>
<ul>
<li>Explanations of difficult topics in your own words</li>
<li>Step-by-step guides through concepts that were challenging</li>
<li>Visual aids or diagrams that helped your understanding</li>
<li>Resources that were most helpful in your learning process</li>
</ul>
<p><strong>3. Technology Comparisons</strong></p>
<p>If you&rsquo;ve used different approaches to solve similar problems:</p>
<ul>
<li>Compare and contrast the technologies or methods</li>
<li>Discuss trade-offs between different solutions</li>
<li>Explain which scenarios might favor one approach over another</li>
<li>Example: &ldquo;Comparing React and Vue for Building a Student Dashboard&rdquo;</li>
</ul>
<p><strong>4. Problem-Solving Documentation</strong></p>
<p>When you solve technical challenges:</p>
<ul>
<li>Document the error or issue you encountered</li>
<li>Explain your debugging process</li>
<li>Share the solution you found</li>
<li>Reflect on what you learned from the experience</li>
</ul>
<p><strong>5. Event Summaries</strong></p>
<p>After attending technical events:</p>
<ul>
<li>Summarize key takeaways</li>
<li>Describe interesting projects or talks</li>
<li>Reflect on how the content relates to your interests</li>
</ul>
<p><strong>6. Learning Resources Collections</strong></p>
<p>Organize helpful resources:</p>
<ul>
<li>Curate lists of useful learning materials</li>
<li>Add your own commentary on why they were valuable</li>
<li>Organize by topic or learning objective</li>
</ul>
<h3 id="posting-frequency">Posting Frequency</h3>
<p>Focus on quality over quantity:</p>
<ul>
<li>Start with a goal of one thoughtful post per month</li>
<li>Maintain consistency rather than posting in bursts</li>
<li>Consider shorter, focused posts (500-800 words) that clearly explain one concept</li>
</ul>
<h3 id="seo-basics-in-plain-english">SEO Basics (In Plain English)</h3>
<p>Search Engine Optimization isn&rsquo;t complex for personal sites. Focus on:</p>
<ol>
<li><strong>Use descriptive titles</strong> - &ldquo;How I Built a Task Management System with React&rdquo; is better than &ldquo;My Project&rdquo;</li>
<li><strong>Write clear summaries</strong> - These appear in search results and social shares</li>
<li><strong>Use relevant tags</strong> - These help organize your content</li>
<li><strong>Link to relevant sources</strong> - Both internal and external</li>
<li><strong>Include code snippets</strong> - These add value and help with technical searches</li>
</ol>
<p>Most importantly: write naturally about topics you understand. Google rewards authentic expertise.</p>
<h3 id="building-an-audience">Building an Audience</h3>
<p>For students, focus on quality over distribution:</p>
<ul>
<li><strong>Share posts in relevant communities</strong> (Reddit, Discord, Slack groups)</li>
<li><strong>Cross-post to DEV.to or Hashnode</strong> (linking back to your site)</li>
<li><strong>Include your site URL in your GitHub profile</strong></li>
<li><strong>Add it to your LinkedIn profile and resume</strong></li>
</ul>
<p>Be patient—audience growth is typically slow at first and then compounds over time.</p>
<h2 id="potential-scenarios">Potential Scenarios</h2>
<p>Here&rsquo;s how a personal website might benefit your job application process:</p>
<h3 id="application-scenario-example">Application Scenario Example</h3>
<p><strong>Without a personal website:</strong></p>
<ul>
<li>Your application consists of a resume listing skills and projects</li>
<li>Reviewer may quickly scan your GitHub repositories but might not explore deeply</li>
<li>Limited time to evaluate your experience based on bullet points</li>
<li>Your application may look similar to many others</li>
</ul>
<p><strong>With a personal website:</strong></p>
<ul>
<li>Your application includes a link to your personal site</li>
<li>Reviewer can see your projects with detailed explanations</li>
<li>Your problem-solving approach is demonstrated through your posts</li>
<li>Your communication skills are evident in your writing</li>
<li>Your portfolio provides specific talking points for interviews</li>
</ul>
<h3 id="what-can-improve-your-application">What Can Improve Your Application</h3>
<p>While every job application is different, having a personal site can potentially:</p>
<ul>
<li>Provide evidence of skills mentioned in your resume</li>
<li>Showcase your communication ability</li>
<li>Demonstrate initiative and follow-through</li>
<li>Give interviewers specific projects to discuss</li>
<li>Show how you think about and solve problems</li>
</ul>
<h3 id="content-suggestions">Content Suggestions</h3>
<p>Consider creating these types of content for your site:</p>
<ol>
<li><strong>Project documentation</strong> with explanations of your approach</li>
<li><strong>Tutorials</strong> for concepts you&rsquo;ve recently learned</li>
<li><strong>Technical challenges</strong> you&rsquo;ve faced and how you solved them</li>
<li><strong>Learning resources</strong> you&rsquo;ve found helpful</li>
<li><strong>Code samples</strong> that demonstrate your skills</li>
</ol>
<p>The most valuable content tends to be authentic, clearly explained, and shows your problem-solving approach.</p>
<h2 id="advanced-features">Advanced Features</h2>
<p>Once you&rsquo;re comfortable with the basics, you can take advantage of more advanced features:</p>
<h3 id="local-development-workflow">Local Development Workflow</h3>
<p>Running Hugo locally gives you instant preview of your changes:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Start the Hugo development server</span>
</span></span><span style="display:flex;"><span>hugo server -D
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># View your site at http://localhost:1313</span>
</span></span></code></pre></div><p>The <code>-D</code> flag shows draft posts, and changes appear immediately as you save files.</p>
<h3 id="creating-custom-sections">Creating Custom Sections</h3>
<p>Beyond blog posts and projects, you might want custom sections like &ldquo;Notes&rdquo; or &ldquo;Resources&rdquo;:</p>
<ol>
<li>Create a directory: <code>content/resources/</code></li>
<li>Add an <code>_index.md</code> file with front matter:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>title: &#34;Resources&#34;
</span></span><span style="display:flex;"><span>description: &#34;Helpful resources I&#39;ve collected&#34;
</span></span><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Introduction text here...
</span></span></code></pre></div><ol start="3">
<li>Add to navigation in <code>hugo.toml</code>:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[[<span style="color:#a6e22e">menu</span>.<span style="color:#a6e22e">main</span>]]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">identifier</span> = <span style="color:#e6db74">&#34;resources&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span> = <span style="color:#e6db74">&#34;Resources&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">url</span> = <span style="color:#e6db74">&#34;/resources/&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">weight</span> = <span style="color:#ae81ff">60</span>
</span></span></code></pre></div><h3 id="understanding-github-actions-workflow">Understanding GitHub Actions Workflow</h3>
<p>The automation that deploys your site is controlled by <code>.github/workflows/deploy.yml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">Deploy Hugo site to Pages</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">on</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">push</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">branches</span>: [<span style="color:#e6db74">&#34;main&#34;</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">workflow_dispatch</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">build</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Checkout</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v3</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">submodules</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>      
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Setup Hugo</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">peaceiris/actions-hugo@v2</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">hugo --minify</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Upload artifact</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/upload-pages-artifact@v1</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">deploy</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">needs</span>: <span style="color:#ae81ff">build</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">permissions</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">pages</span>: <span style="color:#ae81ff">write</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">id-token</span>: <span style="color:#ae81ff">write</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Deploy to GitHub Pages</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/deploy-pages@v1</span>
</span></span></code></pre></div><p>This workflow:</p>
<ol>
<li>Checks out your repository with theme submodules</li>
<li>Sets up Hugo in the environment</li>
<li>Builds your site with optimization</li>
<li>Uploads the built site as an artifact</li>
<li>Deploys the artifact to GitHub Pages</li>
</ol>
<p>You can customize this workflow for advanced needs like scheduled posts or multiple environments.</p>
<h3 id="performance-optimization">Performance Optimization</h3>
<p>The starter template is already optimized, but for even better performance:</p>
<ol>
<li>Compress images before uploading with tools like <a href="https://tinypng.com/">TinyPNG</a></li>
<li>Use Hugo&rsquo;s built-in asset processing for CSS and JS</li>
<li>Lazy-load images for blog posts:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>![<span style="color:#f92672">Alt text</span>](<span style="color:#a6e22e">/images/large-image.jpg &#34;Image title&#34; &#34;loading=lazy&#34;</span>)
</span></span></code></pre></div><ol start="4">
<li>Minimize external dependencies and third-party scripts</li>
</ol>
<p>These optimizations help your site score well on Google&rsquo;s PageSpeed metrics.</p>
<h2 id="common-pitfalls--solutions">Common Pitfalls &amp; Solutions</h2>
<h3 id="i-dont-have-anything-interesting-to-write-about">&ldquo;I don&rsquo;t have anything interesting to write about&rdquo;</h3>
<p><strong>Solution:</strong> Start with what you&rsquo;re learning right now. Explanation is a form of learning, and writing about a concept helps solidify your understanding. Your unique perspective makes even basic topics valuable.</p>
<p><strong>Prompt:</strong> Write about the last bug that took you more than 30 minutes to solve.</p>
<h3 id="my-writing-isnt-good-enough">&ldquo;My writing isn&rsquo;t good enough&rdquo;</h3>
<p><strong>Solution:</strong> Technical writing values clarity over style. Focus on being clear and specific. Use short sentences, include code examples, and explain your thinking. The more you write, the better you&rsquo;ll get.</p>
<p><strong>Pro tip:</strong> Start by outlining with bullet points, then expand each point into a paragraph.</p>
<h3 id="no-one-will-read-my-content">&ldquo;No one will read my content&rdquo;</h3>
<p><strong>Solution:</strong> You&rsquo;re writing for three audiences:</p>
<ol>
<li>Future you (documentation of your learning)</li>
<li>Recruiters and potential employers</li>
<li>Other students facing similar challenges</li>
</ol>
<p>Even if the wider audience never materializes, the first two provide enough value to justify the effort.</p>
<h3 id="technical-issues-during-setup">Technical Issues During Setup</h3>
<p><strong>Problem:</strong> &ldquo;Theme not loading&rdquo;</p>
<p><strong>Solution:</strong> Make sure you initialized the theme submodule:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git submodule update --init --recursive
</span></span></code></pre></div><p><strong>Problem:</strong> &ldquo;Site shows 404 after deployment&rdquo;</p>
<p><strong>Solution:</strong> Check your <code>baseURL</code> in <code>hugo.toml</code>. It must match exactly where your site is deployed (including trailing slash).</p>
<p><strong>Problem:</strong> &ldquo;Changes not showing up&rdquo;</p>
<p><strong>Solution:</strong> Hard refresh your browser (Ctrl+Shift+R or Cmd+Shift+R) to clear cache.</p>
<h2 id="the-bigger-picture">The Bigger Picture</h2>
<p>Building a personal site isn&rsquo;t just about having a portfolio—it&rsquo;s about establishing infrastructure for your career.</p>
<h3 id="building-digital-assets">Building Digital Assets</h3>
<p>Each post, project write-up, and tutorial you create is a digital asset that:</p>
<ul>
<li>Demonstrates your skills and thinking</li>
<li>Helps others solve problems</li>
<li>Improves your own understanding</li>
<li>Builds your professional reputation</li>
<li>Creates serendipitous opportunities through discovery</li>
</ul>
<p>Unlike social media posts that disappear in feeds, your personal site content has longevity and compounds in value.</p>
<h3 id="skills-beyond-the-technical">Skills Beyond the Technical</h3>
<p>Maintaining a personal site develops crucial meta-skills:</p>
<ul>
<li><strong>Technical writing:</strong> Clearly communicating complex ideas</li>
<li><strong>Knowledge management:</strong> Organizing and structuring information</li>
<li><strong>Public learning:</strong> Being comfortable showing your growth</li>
<li><strong>Digital presence management:</strong> Controlling your online narrative</li>
<li><strong>Personal brand development:</strong> Defining how you want to be known</li>
</ul>
<p>These soft skills often differentiate great engineers from good ones.</p>
<h3 id="the-long-game">The Long Game</h3>
<p>Your personal site is a long-term investment with increasing returns:</p>
<ul>
<li><strong>Year 1:</strong> Establish baseline presence and early portfolio</li>
<li><strong>Year 2:</strong> Build content library and refine voice</li>
<li><strong>Year 3:</strong> Leverage for internships and opportunities</li>
<li><strong>Year 4+:</strong> Position as industry contributor with viewpoints</li>
</ul>
<p>Students who start early have a significant advantage by graduation.</p>
<h2 id="next-steps">Next Steps</h2>
<p>Ready to build your personal brand? Here&rsquo;s your action plan:</p>
<ol>
<li><strong>Today:</strong> Fork the repository and deploy your site</li>
<li><strong>This week:</strong> Customize your About page and add a profile photo</li>
<li><strong>Before the weekend:</strong> Write your first blog post or project page</li>
<li><strong>Within 30 days:</strong> Have at least 3 pieces of content published</li>
<li><strong>Ongoing:</strong> Aim for 1-2 new posts per month</li>
</ol>
<h3 id="resources-to-help-you-succeed">Resources to Help You Succeed</h3>
<ul>
<li><strong>Technical Writing:</strong> <a href="https://developers.google.com/tech-writing">Google&rsquo;s Technical Writing Course</a></li>
<li><strong>Markdown Guide:</strong> <a href="https://www.markdownguide.org/">The Markdown Guide</a></li>
<li><strong>Content Ideas:</strong> <a href="https://dev.to/t/beginners">dev.to/t/beginners</a> for inspiration</li>
<li><strong>Hugo Documentation:</strong> <a href="https://gohugo.io/documentation/">Hugo Docs</a></li>
</ul>
<h3 id="commit-to-consistency">Commit to Consistency</h3>
<p>The students who see the biggest impact from their personal sites are those who commit to consistency. It&rsquo;s better to publish one thoughtful post per month than to publish five posts and then abandon your site.</p>
<p>Schedule time for writing just like you schedule study time. Even 30 minutes twice a week is enough to maintain momentum.</p>
<hr>
<p>Your personal brand isn&rsquo;t just what you say about yourself—it&rsquo;s what Google says about you when someone searches your name. With student-branding-starter, you&rsquo;re taking control of that narrative and building a professional presence that will serve you throughout your career.</p>
<p>The best part? You can start today, for free, in just 10 minutes.</p>
<p>Your future self will thank you.</p>
]]></content:encoded></item><item><title>Pokémon Surveys, Serverless Architecture, and Teaching Students to Build on AWS</title><link>https://lukelittle.com/posts/2025/12/pok%C3%A9mon-surveys-serverless-architecture-and-teaching-students-to-build-on-aws/</link><pubDate>Sat, 27 Dec 2025 21:30:00 -0500</pubDate><guid>https://lukelittle.com/posts/2025/12/pok%C3%A9mon-surveys-serverless-architecture-and-teaching-students-to-build-on-aws/</guid><description>Creating a small survey app to teach the cloud and explain its power.</description><content:encoded><![CDATA[<p>Back in November, I was preparing for the Cracking the Cloud presentation at UNC Charlotte. I needed a way to explain how the cloud fundamentally changed what&rsquo;s possible on the internet—not through abstract concepts, but through something students could immediately relate to.</p>
<p>That&rsquo;s when I remembered <strong>Thomas Game Docs</strong>.</p>
<p>If you&rsquo;ve never heard of her: she&rsquo;s a YouTuber who makes incredibly well-produced video essays about video games. And she sometimes runs surveys asking her audience things like &ldquo;Who&rsquo;s the LEAST popular Pokémon?&rdquo; or &ldquo;Who&rsquo;s the LEAST popular Animal Crossing villager?&rdquo;</p>
<p>These aren&rsquo;t small surveys. They get millions of responses.</p>
<p>I helped with the backend for the Pokémon survey—a Flask app on Heroku. But the technology stack wasn&rsquo;t the interesting part. What mattered was that hosting something like this <strong>doesn&rsquo;t require infrastructure expertise anymore</strong>.</p>
<p>She didn&rsquo;t need to buy servers, configure databases, or hire a DevOps team.</p>
<p>Twenty years ago, hosting a survey that could handle 50,000 votes meant:</p>
<ul>
<li>Buy or rent physical servers</li>
<li>Set up database infrastructure</li>
<li>Configure load balancers</li>
<li>Plan for capacity (and hope you got it right)</li>
<li>Deal with outages, scaling issues, and hardware failures</li>
</ul>
<p>All of that would cost thousands of dollars—and that&rsquo;s before you wrote a single line of code.</p>
<p>Today? You build it with Lambda, API Gateway, and DynamoDB. You deploy it with Terraform. And unless traffic gets truly ridiculous, <strong>it costs you basically nothing</strong>.</p>
<p>The cloud didn&rsquo;t just make infrastructure cheaper. It made building things <strong>accessible</strong>.</p>
<p>That&rsquo;s what I wanted students to understand. Not that AWS has a lot of services. But that those services remove the barriers that used to keep people from building.</p>
<hr>
<h2 id="the-demo-a-survey-students-could-actually-participate-in">The demo: a survey students could actually participate in</h2>
<p><img src="/posts/2025/12/pok%C3%A9mon-surveys-serverless-architecture-and-teaching-students-to-build-on-aws/pokemon-surveys-and-cloud-infrastructure_hu_f14e3c0c47ba9455.webp" srcset="/posts/2025/12/pok%C3%A9mon-surveys-serverless-architecture-and-teaching-students-to-build-on-aws/pokemon-surveys-and-cloud-infrastructure_hu_eb463a26a2e45749.webp 750w, /posts/2025/12/pok%C3%A9mon-surveys-serverless-architecture-and-teaching-students-to-build-on-aws/pokemon-surveys-and-cloud-infrastructure_hu_f14e3c0c47ba9455.webp 1500w" sizes="(max-width: 800px) 100vw, 750px"
       width="1500" height="842" alt="Pokémon Surveys and Cloud Infrastructure" loading="lazy" decoding="async"></p>
<p>To drive the point home, I didn&rsquo;t just talk about Thomas Game Docs surveys.</p>
<p>I had the students take one.</p>
<p>At the start of the presentation, I pulled up a simple survey asking about their exposure to AWS:</p>
<ul>
<li>Have you used AWS before?</li>
<li>Have you deployed something to the cloud?</li>
</ul>
<p>They voted. They saw the results update in real-time. And then I showed them <strong>exactly how it worked</strong>—with no servers running, no databases to manage, and no ongoing costs to worry about.</p>
<p>That survey? It&rsquo;s the same repo I&rsquo;m writing about now: <a href="https://github.com/lukelittle/cracking-the-cloud">cracking-the-cloud</a>.</p>
<h2 id="the-architecture-small-but-real">The architecture (small, but real)</h2>
<p><img src="/posts/2025/12/pok%C3%A9mon-surveys-serverless-architecture-and-teaching-students-to-build-on-aws/diagram_hu_67a3240d858eed62.webp" srcset="/posts/2025/12/pok%C3%A9mon-surveys-serverless-architecture-and-teaching-students-to-build-on-aws/diagram_hu_811848a8f1ebf8fa.webp 750w, /posts/2025/12/pok%C3%A9mon-surveys-serverless-architecture-and-teaching-students-to-build-on-aws/diagram_hu_67a3240d858eed62.webp 1500w" sizes="(max-width: 800px) 100vw, 750px"
       width="1500" height="932" alt="Architecture Diagram" loading="lazy" decoding="async"></p>
<p>Here&rsquo;s the final shape of the system:</p>
<ul>
<li><strong>S3</strong> hosts the static frontend (HTML, CSS, JS)</li>
<li><strong>CloudFront</strong> sits in front for HTTPS, caching, and global delivery</li>
<li><strong>API Gateway</strong> exposes a REST API</li>
<li><strong>Lambda</strong> handles business logic (vote, results, reset)</li>
<li><strong>DynamoDB</strong> stores votes</li>
<li><strong>IAM</strong> wires permissions together</li>
<li><strong>Terraform</strong> defines everything</li>
</ul>
<p>No servers. No containers. No databases to patch. No stateful nonsense.</p>
<p>Just managed services doing exactly what they&rsquo;re good at.</p>
<p>The request flow looks like this:</p>
<p>User clicks a button → JavaScript calls the API → API Gateway invokes Lambda → Lambda writes to DynamoDB → Response goes back to the browser.</p>
<p>Simple. Explicit. Observable.</p>
<h2 id="why-static-frontend--api-on-purpose">Why static frontend + API (on purpose)</h2>
<p>I didn&rsquo;t use React.<br>
I didn&rsquo;t use Next.js.<br>
I didn&rsquo;t use server-side rendering.</p>
<p>Not because those tools are bad—they&rsquo;re not. But because I wanted you to see what&rsquo;s actually happening.</p>
<p>When you open the frontend code, you can immediately see:</p>
<ul>
<li>where the API URL lives</li>
<li>how a POST request is formed</li>
<li>what the response looks like</li>
<li>how the browser handles the data</li>
</ul>
<p>No build steps. No transpilation. No abstractions hiding what&rsquo;s really going on.</p>
<p>Once you understand how a browser talks to an API using vanilla JavaScript, <strong>then</strong> you can add React, TypeScript, and all the modern tooling. But you&rsquo;ll know what those tools are doing for you—not just that they work.</p>
<p>The frontend&rsquo;s job here is to show you the fundamentals, not teach you the latest framework.</p>
<h2 id="the-lambdas-three-on-purpose">The Lambdas (three, on purpose)</h2>
<p>There are three Lambda functions:</p>
<ol>
<li><strong>Vote</strong> (<code>backend/vote.py</code>) – Processes vote submissions</li>
<li><strong>Results</strong> (<code>backend/results.py</code>) – Retrieves vote counts</li>
<li><strong>Reset</strong> (<code>backend/reset.py</code>) – Clears all data</li>
</ol>
<p>Could this be one Lambda with a switch statement?<br>
Absolutely.</p>
<p>Did I do that?<br>
Absolutely not.</p>
<p>Each function has:</p>
<ul>
<li>one responsibility</li>
<li>one API route</li>
<li>one IAM policy</li>
</ul>
<p>This lets students see how permissions map to behavior.</p>
<ul>
<li>The <strong>vote</strong> function can write, but not delete</li>
<li>The <strong>results</strong> function can read, but not write</li>
<li>The <strong>reset</strong> function can delete, but nothing else</li>
</ul>
<p>You don&rsquo;t need a lecture on least privilege when the code makes it obvious.</p>
<h2 id="how-voting-actually-works">How voting actually works</h2>
<p>When a student clicks a vote button, here&rsquo;s the journey that request takes through the serverless stack:</p>
<pre class="mermaid">sequenceDiagram
    participant User as 👤 User Browser
    participant S3 as 🪣 S3 + CloudFront
    participant APIG as 🌐 API Gateway
    participant Lambda as ⚡ vote.py
    participant DDB as 🗄️ DynamoDB
    
    User-&gt;&gt;S3: GET /vote.html
    S3--&gt;&gt;User: HTML + JavaScript
    
    Note over User: User clicks vote button&lt;br/&gt;sessionId generated (UUID)&lt;br/&gt;stored in sessionStorage
    
    User-&gt;&gt;APIG: POST /vote&lt;br/&gt;{ &#34;vote&#34;: &#34;aws&#34;, &#34;sessionId&#34;: &#34;abc123&#34; }
    APIG-&gt;&gt;Lambda: Invoke vote function
    
    Note over Lambda: Validate sessionId exists&lt;br/&gt;Validate vote in [&#39;no&#39;, &#39;aws&#39;, &#39;other&#39;]
    
    Lambda-&gt;&gt;DDB: PutItem&lt;br/&gt;{ id: &#34;abc123&#34;, vote: &#34;aws&#34; }
    Note over DDB: Overwrites if sessionId&lt;br/&gt;already voted&lt;br/&gt;(allows vote changes)
    DDB--&gt;&gt;Lambda: Success
    
    Lambda--&gt;&gt;APIG: 200 OK&lt;br/&gt;{ &#34;message&#34;: &#34;Vote recorded&#34; }
    APIG--&gt;&gt;User: Response
    
    Note over User: JavaScript displays&lt;br/&gt;&#34;Vote recorded!&#34; message
</pre>

<p>The critical piece here is the <strong>sessionId</strong>. It&rsquo;s a random UUID generated in the browser and stored in <code>sessionStorage</code>—which means it persists for the current tab but disappears when you close the browser.</p>
<p>This gives us:</p>
<ul>
<li><strong>One vote per browser session</strong> – you can&rsquo;t spam-click the vote button</li>
<li><strong>Vote changes allowed</strong> – if you vote &ldquo;No experience&rdquo; and change your mind, the second vote overwrites the first (DynamoDB&rsquo;s <code>PutItem</code> does this automatically)</li>
<li><strong>Privacy by default</strong> – no accounts, no tracking, no persistent identifiers</li>
<li><strong>Simple anti-spam</strong> – good enough for a teaching demo</li>
</ul>
<p>Could someone bypass this by opening incognito windows? Yes. Is that fine for a teaching app? Also yes. The point is showing how to prevent duplicate votes, not building a production election system.</p>
<p>Here&rsquo;s what <code>vote.py</code> actually looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">handler</span>(event, context):
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Parse the incoming request</span>
</span></span><span style="display:flex;"><span>    body <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>loads(event<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;body&#39;</span>, <span style="color:#e6db74">&#39;</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#39;</span>))
</span></span><span style="display:flex;"><span>    vote_option <span style="color:#f92672">=</span> body<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;vote&#39;</span>)
</span></span><span style="display:flex;"><span>    session_id <span style="color:#f92672">=</span> body<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;sessionId&#39;</span>)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Validate inputs</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> session_id:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;statusCode&#39;</span>: <span style="color:#ae81ff">400</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;body&#39;</span>: json<span style="color:#f92672">.</span>dumps({<span style="color:#e6db74">&#39;message&#39;</span>: <span style="color:#e6db74">&#39;Session ID is required&#39;</span>})
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> vote_option <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> [<span style="color:#e6db74">&#39;no&#39;</span>, <span style="color:#e6db74">&#39;aws&#39;</span>, <span style="color:#e6db74">&#39;other&#39;</span>]:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;statusCode&#39;</span>: <span style="color:#ae81ff">400</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;body&#39;</span>: json<span style="color:#f92672">.</span>dumps({<span style="color:#e6db74">&#39;message&#39;</span>: <span style="color:#e6db74">&#39;Invalid vote option&#39;</span>})
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Store the vote</span>
</span></span><span style="display:flex;"><span>    table<span style="color:#f92672">.</span>put_item(Item<span style="color:#f92672">=</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;id&#39;</span>: session_id,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;vote&#39;</span>: vote_option
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;statusCode&#39;</span>: <span style="color:#ae81ff">200</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;headers&#39;</span>: {<span style="color:#e6db74">&#39;Access-Control-Allow-Origin&#39;</span>: <span style="color:#e6db74">&#39;*&#39;</span>},
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;body&#39;</span>: json<span style="color:#f92672">.</span>dumps({<span style="color:#e6db74">&#39;message&#39;</span>: <span style="color:#e6db74">&#39;Vote recorded successfully&#39;</span>})
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>Twenty lines of code. No ORM. No database migrations. No connection pooling. Just write to DynamoDB and return a response.</p>
<h2 id="how-results-actually-work">How results actually work</h2>
<p>The results page is where students first encounter the concept of scanning a database:</p>
<pre class="mermaid">sequenceDiagram
    participant User as 👤 User Browser
    participant S3 as 🪣 S3 + CloudFront
    participant APIG as 🌐 API Gateway
    participant Lambda as ⚡ results.py
    participant DDB as 🗄️ DynamoDB
    
    User-&gt;&gt;S3: GET /results.html
    S3--&gt;&gt;User: HTML + JavaScript + Chart.js
    
    Note over User: Page loads&lt;br/&gt;JavaScript calls API
    
    User-&gt;&gt;APIG: GET /results
    APIG-&gt;&gt;Lambda: Invoke results function
    
    Lambda-&gt;&gt;DDB: Scan table&lt;br/&gt;ProjectionExpression=&#39;vote&#39;
    Note over DDB: Returns all vote values&lt;br/&gt;[&#39;aws&#39;, &#39;no&#39;, &#39;aws&#39;, &#39;other&#39;, ...]
    
    DDB--&gt;&gt;Lambda: Page 1 of results
    
    Note over Lambda: Check for LastEvaluatedKey&lt;br/&gt;(pagination if table &gt; 1MB)
    
    loop While LastEvaluatedKey exists
        Lambda-&gt;&gt;DDB: Scan with ExclusiveStartKey
        DDB--&gt;&gt;Lambda: Next page of results
    end
    
    Note over Lambda: Count votes using Counter&lt;br/&gt;{ &#39;no&#39;: 15, &#39;aws&#39;: 42, &#39;other&#39;: 8 }
    
    Lambda--&gt;&gt;APIG: 200 OK&lt;br/&gt;{ &#34;no&#34;: 15, &#34;aws&#34;: 42, &#34;other&#34;: 8 }
    APIG--&gt;&gt;User: Response
    
    Note over User: Chart.js renders&lt;br/&gt;vote counts as bar chart
</pre>

<p>The interesting part here is <strong>pagination</strong>. DynamoDB&rsquo;s <code>Scan</code> operation returns a maximum of 1MB of data per request. If your table is larger than that, you get a <code>LastEvaluatedKey</code> in the response, which you use to fetch the next page.</p>
<p>Here&rsquo;s what that looks like in code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">handler</span>(event, context):
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># First scan</span>
</span></span><span style="display:flex;"><span>    response <span style="color:#f92672">=</span> table<span style="color:#f92672">.</span>scan(ProjectionExpression<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;vote&#39;</span>)
</span></span><span style="display:flex;"><span>    items <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;Items&#39;</span>, [])
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Keep scanning if there&#39;s more data</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> <span style="color:#e6db74">&#39;LastEvaluatedKey&#39;</span> <span style="color:#f92672">in</span> response:
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> table<span style="color:#f92672">.</span>scan(
</span></span><span style="display:flex;"><span>            ProjectionExpression<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;vote&#39;</span>,
</span></span><span style="display:flex;"><span>            ExclusiveStartKey<span style="color:#f92672">=</span>response[<span style="color:#e6db74">&#39;LastEvaluatedKey&#39;</span>]
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        items<span style="color:#f92672">.</span>extend(response<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;Items&#39;</span>, []))
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Extract just the vote values</span>
</span></span><span style="display:flex;"><span>    votes <span style="color:#f92672">=</span> [item[<span style="color:#e6db74">&#39;vote&#39;</span>] <span style="color:#66d9ef">for</span> item <span style="color:#f92672">in</span> items]
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Count them</span>
</span></span><span style="display:flex;"><span>    vote_counts <span style="color:#f92672">=</span> Counter(votes)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Return with defaults for zero-vote options</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;statusCode&#39;</span>: <span style="color:#ae81ff">200</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;headers&#39;</span>: {<span style="color:#e6db74">&#39;Access-Control-Allow-Origin&#39;</span>: <span style="color:#e6db74">&#39;*&#39;</span>},
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;body&#39;</span>: json<span style="color:#f92672">.</span>dumps({
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;no&#39;</span>: vote_counts<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;no&#39;</span>, <span style="color:#ae81ff">0</span>),
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;aws&#39;</span>: vote_counts<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;aws&#39;</span>, <span style="color:#ae81ff">0</span>),
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;other&#39;</span>: vote_counts<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;other&#39;</span>, <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>Students immediately see three concepts:</p>
<ul>
<li><strong>Scanning costs</strong> – you&rsquo;re reading the entire table, which is fine for 100 votes but would be expensive for 10 million</li>
<li><strong>Pagination handling</strong> – real-world data doesn&rsquo;t fit in one response</li>
<li><strong>Aggregation happens in code</strong> – DynamoDB doesn&rsquo;t have <code>COUNT(*) GROUP BY vote</code>, so you pull the data and count it yourself</li>
</ul>
<p>This naturally leads to questions like &ldquo;how would you make this more efficient?&rdquo; which is exactly where you want students&rsquo; brains to go.</p>
<h2 id="how-reset-actually-works">How reset actually works</h2>
<p>The reset function is the most dangerous one in the app—and also the most instructive:</p>
<pre class="mermaid">sequenceDiagram
    participant User as 👤 User Browser
    participant S3 as 🪣 S3 + CloudFront
    participant APIG as 🌐 API Gateway
    participant Lambda as ⚡ reset.py
    participant DDB as 🗄️ DynamoDB
    
    User-&gt;&gt;S3: GET /reset.html
    S3--&gt;&gt;User: HTML + JavaScript
    
    Note over User: User clicks&lt;br/&gt;&#34;Reset All Votes&#34; button&lt;br/&gt;(⚠️ Destructive operation)
    
    User-&gt;&gt;APIG: POST /reset
    APIG-&gt;&gt;Lambda: Invoke reset function
    
    Lambda-&gt;&gt;DDB: Scan table&lt;br/&gt;ProjectionExpression=&#39;id&#39;
    Note over DDB: Only return IDs&lt;br/&gt;(need keys to delete)
    DDB--&gt;&gt;Lambda: All item IDs
    
    loop While LastEvaluatedKey exists
        Lambda-&gt;&gt;DDB: Scan with ExclusiveStartKey
        DDB--&gt;&gt;Lambda: More IDs
    end
    
    Note over Lambda: Batch delete in groups of 25&lt;br/&gt;(DynamoDB batch write limit)
    
    loop For each batch of 25 items
        Lambda-&gt;&gt;DDB: BatchWriteItem&lt;br/&gt;Delete items
        DDB--&gt;&gt;Lambda: Batch delete success
    end
    
    Note over Lambda: All items deleted&lt;br/&gt;Table is now empty
    
    Lambda--&gt;&gt;APIG: 200 OK&lt;br/&gt;{ &#34;message&#34;: &#34;Survey reset&#34; }
    APIG--&gt;&gt;User: Response
    
    Note over User: &#34;All votes deleted!&#34;
</pre>

<p>This introduces batch operations:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">handler</span>(event, context):
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Scan for all IDs (we only need keys to delete)</span>
</span></span><span style="display:flex;"><span>    scan_response <span style="color:#f92672">=</span> table<span style="color:#f92672">.</span>scan(ProjectionExpression<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;id&#39;</span>)
</span></span><span style="display:flex;"><span>    items <span style="color:#f92672">=</span> scan_response<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;Items&#39;</span>, [])
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Handle pagination</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> <span style="color:#e6db74">&#39;LastEvaluatedKey&#39;</span> <span style="color:#f92672">in</span> scan_response:
</span></span><span style="display:flex;"><span>        scan_response <span style="color:#f92672">=</span> table<span style="color:#f92672">.</span>scan(
</span></span><span style="display:flex;"><span>            ProjectionExpression<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;id&#39;</span>,
</span></span><span style="display:flex;"><span>            ExclusiveStartKey<span style="color:#f92672">=</span>scan_response[<span style="color:#e6db74">&#39;LastEvaluatedKey&#39;</span>]
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        items<span style="color:#f92672">.</span>extend(scan_response<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;Items&#39;</span>, []))
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Delete all items using batch writer</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> items:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">with</span> table<span style="color:#f92672">.</span>batch_writer() <span style="color:#66d9ef">as</span> batch:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">for</span> item <span style="color:#f92672">in</span> items:
</span></span><span style="display:flex;"><span>                batch<span style="color:#f92672">.</span>delete_item(Key<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#39;id&#39;</span>: item[<span style="color:#e6db74">&#39;id&#39;</span>]})
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;statusCode&#39;</span>: <span style="color:#ae81ff">200</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;headers&#39;</span>: {<span style="color:#e6db74">&#39;Access-Control-Allow-Origin&#39;</span>: <span style="color:#e6db74">&#39;*&#39;</span>},
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;body&#39;</span>: json<span style="color:#f92672">.</span>dumps({<span style="color:#e6db74">&#39;message&#39;</span>: <span style="color:#e6db74">&#39;Survey reset successfully&#39;</span>})
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>The <code>batch_writer()</code> context manager is doing a lot of hidden work:</p>
<ul>
<li>Groups deletes into batches of 25 (DynamoDB&rsquo;s limit)</li>
<li>Automatically retries failed operations</li>
<li>Handles throttling gracefully</li>
<li>Only commits when the context exits</li>
</ul>
<p>Students don&rsquo;t need to know all of that on day one, but they can see that deleting 100 items doesn&rsquo;t require 100 API calls.</p>
<p><strong>Important note:</strong> In a real app, you&rsquo;d absolutely add authentication and authorization here. This function is intentionally unprotected for teaching purposes—it demonstrates the mechanics of batch operations without the complexity of auth flows.</p>
<h2 id="dynamodb-intentionally-unsexy">DynamoDB (intentionally unsexy)</h2>
<p>The DynamoDB table is <strong>boring by design</strong>.</p>
<ul>
<li>Partition key (<code>id</code>)</li>
<li>Simple attributes (<code>vote</code>)</li>
<li>No GSIs</li>
<li>No streams</li>
<li>No TTL magic</li>
</ul>
<p>Why?</p>
<p>Because the lesson isn&rsquo;t &ldquo;DynamoDB is infinite and weird.&rdquo;<br>
The lesson is:</p>
<p><strong>You can persist state without running a database.</strong></p>
<p>Once students are comfortable, <em>then</em> you add:</p>
<ul>
<li>secondary indexes</li>
<li>conditional writes</li>
<li>access patterns</li>
<li>cost modeling</li>
</ul>
<p>But not on day one.</p>
<h2 id="terraform-as-the-real-curriculum">Terraform as the real curriculum</h2>
<p>Here&rsquo;s the quiet truth: <strong>The Terraform is the most important part of this project.</strong></p>
<p>Students don&rsquo;t learn AWS by clicking around the console. They learn AWS by reading infrastructure definitions and realizing: <em>&ldquo;Oh—that&rsquo;s what connects to that.&rdquo;</em></p>
<p>This repo forces them to see how API Gateway connects to Lambda (<code>aws_api_gateway_integration</code>), how Lambda permissions work (<code>aws_lambda_permission</code>), how CloudFront talks to S3 (<code>origin_access_identity</code>), how outputs become frontend configuration (<code>cloudfront_domain</code>).</p>
<p>They can delete everything and recreate it in minutes. That alone teaches more than most cloud courses.</p>
<hr>
<h2 id="how-does-this-compare-to-the-heroku-version">How does this compare to the Heroku version?</h2>
<p>Remember the Flask app I built for Thomas Game Docs&rsquo; Pokémon survey?</p>
<p>I monitored that deployment closely. Every time she dropped an announcement on social media—Twitter, YouTube community posts, etc.—I watched the app response time blow up. We were constantly aware that we were one viral tweet away from needing to manually scale the Heroku dyno or upgrade the database.</p>
<p>With this serverless version? <strong>There wouldn&rsquo;t have been a hiccup.</strong></p>
<p>Lambda would have spun up as many concurrent executions as needed. API Gateway would have handled the traffic without breaking a sweat. DynamoDB would have throttled gracefully and auto-scaled. CloudFront would have cached the static assets globally.</p>
<p>No monitoring dashboards.<br>
No capacity planning.<br>
No &ldquo;should we upgrade now or wait?&rdquo; decisions.<br>
No watching metrics at 2 AM when a post goes viral.</p>
<p>The infrastructure would have scaled to meet demand and then scaled back down when traffic dropped. And the bill would have stayed under $5 for the entire campaign.</p>
<p>That&rsquo;s the difference between &ldquo;serverless&rdquo; and &ldquo;server-you-manage-less.&rdquo;</p>
<h2 id="costs-because-someone-always-asks">Costs (because someone always asks)</h2>
<ul>
<li><strong>S3</strong>: pennies</li>
<li><strong>CloudFront</strong>: free tier</li>
<li><strong>Lambda</strong>: free tier</li>
<li><strong>API Gateway</strong>: free tier</li>
<li><strong>DynamoDB</strong>: free tier</li>
</ul>
<p><strong>Total monthly cost for light usage</strong>: effectively $0.</p>
<p>Which matters, because students shouldn&rsquo;t need a credit card panic attack to learn cloud fundamentals.</p>
<h2 id="if-you-want-to-fork-it">If you want to fork it</h2>
<p>The entire project is open source and designed to be broken, modified, and rebuilt:</p>
<p><strong><a href="https://github.com/lukelittle/cracking-the-cloud">github.com/lukelittle/cracking-the-cloud</a></strong></p>
<hr>
<h2 id="how-this-project-can-be-used-to-teach-cloud">How this project can be used to teach cloud</h2>
<p>The beauty of this baseline is that every extension becomes a teaching moment. The answer to nearly every &ldquo;Can I add&hellip;?&rdquo; question is: <strong>yes</strong>.</p>
<p><strong>&ldquo;Can I add another survey question?&rdquo;</strong> Yes. Modify the DynamoDB schema and update the frontend. Students learn about schema evolution and backwards compatibility.</p>
<p><strong>&ldquo;Can I add authentication?&rdquo;</strong> Yes. Add Cognito, modify the Lambda to verify JWT tokens, update the frontend to handle login flows. Students learn about identity providers, token validation, and authorization.</p>
<p><strong>&ldquo;Can I track who voted when?&rdquo;</strong> Yes. Add timestamps to DynamoDB items, maybe stream changes to S3 for analytics. Students learn about audit trails and data retention.</p>
<p><strong>&ldquo;Can I swap DynamoDB for RDS?&rdquo;</strong> Yes. But now you need VPCs, security groups, connection pooling, and Lambda cold start considerations. Students learn why DynamoDB was the right choice for this use case.</p>
<p><strong>&ldquo;Can I add email notifications?&rdquo;</strong> Yes. Give a Lambda permission to use SES, trigger it from DynamoDB Streams. Students learn about event-driven architecture and service integration.</p>
<p><strong>&ldquo;Can I add a CI/CD pipeline?&rdquo;</strong> Yes. Add GitHub Actions, IAM roles with OIDC, and S3 sync logic. Students learn about deployment automation and security best practices.</p>
<p>Because the baseline is so small, every addition is visible. Every new service has a before-and-after moment. This is where the app stops being a demo and starts being a scaffold—students can extend it in any direction and immediately see what changes.</p>
<p>Change the question. Add auth. Add metrics. Rip it apart. That&rsquo;s how you build instincts. Not by memorizing services, but by wiring them together and watching what happens.</p>
]]></content:encoded></item></channel></rss>