Skip to main content
Understanding How Rendering Works in Web Development

Understanding How Rendering Works in Web Development

When I built my portfolio in Astro, I started with Static Site Generation because it’s Astro’s default and it’s fast. As the site grew, I ran into situations where SSG wasn’t enough, and I had to learn when to reach for Server Side Rendering or Client Side Rendering instead. The distinction between the three seems straightforward on paper, but in practice it tripped me up more than I expected.

Static site generation (SSG)

Static Site Generation is Astro’s default. Pages get generated at build time, which makes them fast since there’s no server processing on each request. This works well for content that doesn’t change often, like my Experience and Projects pages. The downside is that any update requires a new build.

The simplest example I can think of is the copyright date:

---
const today = new Date();
---

    <footer>
        Copyright &copy; {today.getFullYear()} Sam Packer
    </footer>

Right now, it says 2025. However, on an SSG site, it will not update to 2026 on New Year’s. The new Date() ran at build time and got baked into the HTML. If I want it to say 2026, I have to rebuild the site.

Server-side rendering (SSR)

Server Side Rendering runs the same kind of code, but on every page load instead of just at build time. The content stays fresh without needing a rebuild.

A good example is scheduled blog posts. Say I want a post to automatically go live at a future date. With SSG, this doesn’t work. I can write filtering code to only show posts published before the current date and time, and Astro won’t stop me:

---
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";

dayjs.extend(utc);

const posts = (await getCollection("blog"))
    .filter(post => {
        const postDate = dayjs(post.data.pubDate).utc();
        return postDate.isBefore(currentDate, "second") || postDate.isSame(currentDate, "second");
    })
    .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---

    {
        posts.map((post) => (
            <BlogCard post={post}/>
        ))
    }

But the filtering only runs at build time. The post won’t actually appear until I rebuild, regardless of what the code says. With SSR, that filtering code runs on every page load, so posts go live when they’re supposed to.

The mental model is straightforward: any code inside the --- fences at the top of an Astro file will run. The question is whether it runs once at build time (SSG) or on every request (SSR).

Client-side rendering (CSR)

Client Side Rendering is for things that need to run in the user’s browser. My first CSR component was the contact form. The form itself renders on the client, but it calls a server-side function to verify the CAPTCHA and send the email. You wouldn’t want the client handling that part directly.

I also built a popup system that uses CSR. Since I need to track whether a user has already seen a popup, I store that state in a cookie, which is tied to the browser. I haven’t actually used popups on the site yet, but the system is there if I need it.

In Astro, CSR components get imported in the frontmatter but are used in the content area with a client: directive. Here’s what my contact page looks like:

---
import ContactForm from "../components/ContactForm.svelte";

---

    <h2>Contact</h2>
<p>Thank you for reaching out! You may contact me using this form below. I will get back to you
    shortly. You may also contact me on LinkedIn as well.</p>

<ContactForm client:load/>

You might notice there’s no form in the Astro file. It’s inside the Svelte component, along with the CAPTCHA and all the state management. The client:load directive is what tells Astro to load this component on the client side. Could I put the form directly in the Astro file? Yes, but the CAPTCHA needs to run client-side to verify the user is human. It’s easier to keep the whole thing in one Svelte component since we’re verifying the CAPTCHA and giving the user feedback on submission in the same place. There are other client directives for different loading strategies, which you can find in the Astro documentation.

Comparing SSG, SSR, and CSR

Here’s a quick reference for the three rendering methods:

FeatureSSG (Static Site Generation)SSR (Server Side Rendering)CSR (Client Side Rendering)
PerformanceFastest, pre-generated HTMLSlower than SSG, depends on server response timeSlower initial load, faster navigation
Content FreshnessRequires rebuild for updatesDynamically updated per requestUpdated dynamically on user interaction
Server DependencyNo server needed after buildRequires a running serverNo server required for rendering, but needed for APIs
SEO BenefitsExcellent, pre-rendered HTMLGood, as content is server-renderedPoor, content loads via JavaScript
Best Use CasesBlogs, documentation, marketing pagesUser dashboards, scheduled publishing, authenticationInteractive forms, real time updates, user-specific behavior

When it goes wrong

One of the more recent issues I ran into was blog post dates displaying in UTC instead of the user’s local timezone. My first instinct was to fix it server-side, but the server can’t easily detect a user’s timezone. I spent hours debugging why the timezone detection wouldn’t work before it finally clicked: the conversion needed to happen in the browser, not on the server. I moved the logic to a Svelte component, and the dates started displaying correctly.

It sounds obvious in hindsight, but “this needs to run on the client” wasn’t where my head went initially. That’s the thing about rendering modes. The code looks the same regardless of where it runs. The only difference is when and where it executes, and when you get that wrong, the bugs are confusing because the code itself looks correct.

How I split it up

My portfolio uses all three. Static generation for pages I update manually, like Experience. Server-side rendering for dynamic content like my blog, where posts need to go live on schedule. Client-side rendering for interactive elements that depend on the browser, like the contact form and timezone detection.

Astro gives you the flexibility to mix all three in one project, but that means you need to understand what runs where. The learning curve is real. However, once it clicks, the control you get over how your site behaves is worth the effort.