Lift off
Learning Astro Tools
I’ve worked with great developers. I am not one.
I’ve played around with differnt languages, stacks and frameworks a ton. I’ve taken tutorials. I’ve done what I’ve had to support sales engineering, marketing and documentation efforts during my career. But, I’m no developer.
I’m an amateur, a hobbyist. I’ve built personal stuff in MERN, published documentation and marketing sites using Gatsby, Next.js and Hugo. I’m dangerously incompetent w/ Javascript. I’m a bit of a hack when it comes to actual development.
What is Astro
Astro.Build is a relative newcomer in the web framework space. Lets ask Chat GPT:
Astro is a modern web framework designed for building fast, content-focused websites. It emphasizes performance, simplicity, and flexibility, making it ideal for static sites, blogs, and other content-heavy projects. Here’s a brief overview:
Key Features of Astro:
Component-Driven: Astro allows you to build websites using UI components, leveraging popular frameworks like React, Vue, Svelte, or even plain HTML and JavaScript. You can mix and match components from different frameworks within the same project.
Islands Architecture: Astro introduces the concept of “islands architecture,” where only the necessary JavaScript is shipped to the client. Non-interactive parts of the page are rendered as static HTML, reducing the amount of JavaScript sent to the browser and improving performance.
Zero JavaScript by Default: By default, Astro renders your content to static HTML, with zero JavaScript on the client side unless explicitly needed. This approach significantly improves load times and performance.
Markdown Support: Astro has built-in support for Markdown, making it easy to write content-driven pages. You can also enhance Markdown files with React, Vue, or other components.
SSR and Static Site Generation: Astro supports both Static Site Generation (SSG) and Server-Side Rendering (SSR), giving you flexibility depending on your project’s needs.
Integrations: Astro offers a wide range of integrations for popular tools and libraries, such as Tailwind CSS, Sass, and various CMS platforms, allowing you to customize your development environment.
Deploy Anywhere: Astro generates highly optimized static assets, making it easy to deploy your site on any platform, including traditional hosting services, serverless environments, and content delivery networks (CDNs).
…
Astro is designed to deliver a highly performant and flexible development experience, making it a strong choice for modern web projects that prioritize speed and simplicity.
That sounds about right.
My Experience
I had no trouble learning the basics, standing up a sandbox, finding a site template, customizing some of the capabilities of the template and publishing this site. It took less than 8 hours (with some significant help from my AI mentors at Chat GPT).
I skimmed the documentation at Astro.Build. I ran npm create astro@latest
I poked around and liked it. I searched for a template and found Astrofy a free template by a friendly stranger Manuel Ernesto Garcia. It looked interesting. I needed to update my personal site.
Then I got busy.
Customizing Astrofy
Updating the CV to handle Markdown
I love markdown. (I’m writing this in Markdown) Sure it has limitations, but for capturing formatted content it really can’t be beat. I’ve been using Obsidian for my notes for years and am comfortable with it. One of the great things about Astro.Build is it’s native support for Markdown.
But, The CV page in the Astrofy template didn’t support markdown. It was beautifully structured and had some great design constructs. But, it was written in HTML + Tailwind CSS. I don’t really like writing long documents in HTML.
My Solution
I created a directory for VC content and a file for each of my positions. Writing in markdown made structuring and formatting this content super easy.
Here’s an example:
# resumeSection3.md
---
title: Blogger at brianross.io
subTitle: From now to later
---
I wrote about myself. Original, eh?
## Accomplishment
- Customized a thing
- Wrote a thing
- Deployed a thing
Led to a questionable amount of views (I haven't added any tracking yet).
I updated the cv.astro page to ingest MD files of each of my jobs. It was simple in Astro. I looped over the posts using Astro.glob()
and populated the formatted page section.
#cv.Astro page updates
---
// Content Deleted
// Astro.glob pulls in all my articles in the cvcontent directory
const sections = await Astro.glob("../content/cvcontent/*.md");
---
<BaseLayout title='Resume' sideBarActiveItemID='cv'>
<!-- Deleted Content -->
<div class='mb-5'>
<div class='text-3xl text-secondary w-full font-bold'>Experience</div>
</div>
<div class='time-line-container mb-10 text-primary-content'>
{
sections.map((section) => (
<TimeLineElement
title={section.frontmatter.title}
subtitle={section.frontmatter.subTitle}
>
<article class='prose text-primary-content lg:prose-base'>
<section.Content />
</article>
</TimeLineElement>
))
}
</div>
</BaseLayout>
Now I can simply tweak markdown pages to add or edit the descriptions of my different jobs.
Download PDF
I’m old school. I like PDF’s of resumes and I wanted to make one of my resume available. But I wanted only one source of truth - this site. So, once I completed a version of my resume I got working on creating a PDF of it. Here’s what a sorted out:
- Install puppeteer puppeteer.
npm install puppeteer
- Create a script (see below for the final version)
- Make sure the dev server is running (it’s what will render the page that is saved as a pdf)
npm run dev
- Opened a new terminal, navigate to the script and run
node run <scriptname>.js
This took a few iterations to get right. The output looked wonky for awhile. I asked Chat.GPT for help. It helped! The trickiest part was asserting a new style. This template uses both Tailwind CSS and Daisy UI. After a bit of tweaking I got it sorted.
The Script written by AI and I:
import puppeteer from "puppeteer";
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Capture browser console logs in Node.js console
page.on("console", (msg) => console.log("PAGE LOG:", msg.text()));
page.on("requestfailed", (request) => {
console.log("Request failed:", request.url(), request.failure().errorText);
});
// Navigate to the page where your Astro site is running
await page.goto("http://localhost:4321/cv", { waitUntil: "networkidle2" });
// Evaluate the page to set the theme and log the current theme
await page
.evaluate(async () => {
// This was the tough bit; needed to swap out the theme so it would
// look OK in a PDF
const currentTheme = document.documentElement.getAttribute("data-theme");
console.log(`Current theme: ${currentTheme}`); // This log will now be captured
// Set the new theme
document.documentElement.setAttribute("data-theme", "light");
// Adding a delay to ensure theme application
await new Promise((resolve) => setTimeout(resolve, 1000));
// Verify the new theme
return document.documentElement.getAttribute("data-theme");
})
.then((theme) => {
console.log(`Theme set to: ${theme}`);
});
// Extract the full HTML and CSS from the #resume div
// This was also challenging; at first no styles were applied because
// I didn't want the whole site rendered - just one DIV
const resumeContent = await page.evaluate(() => {
const resumeDiv = document.getElementById("resume");
if (resumeDiv) {
const content = resumeDiv.outerHTML;
const newDocument = document.implementation.createHTMLDocument();
newDocument.body.innerHTML = content;
// Import all stylesheets and inline styles
document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
const newLink = newDocument.createElement("link");
newLink.rel = "stylesheet";
newLink.href = link.href;
newDocument.head.appendChild(newLink);
});
document.querySelectorAll("style").forEach((style) => {
const newStyle = newDocument.createElement("style");
newStyle.innerHTML = style.innerHTML;
newDocument.head.appendChild(newStyle);
});
// Add page styling - Thanks Chat GPT - this helped format the
// output for letter sized paper.
const pageStyle = newDocument.createElement("style");
pageStyle.innerHTML = `
@page {
size: letter;
margin: 1in;
}
body {
box-sizing: border-box;
margin: 0;
padding: 0;
width: 100%;
}
#resume {
width: 100%;
margin: 0;
padding: 0;
}
h1 + div, h2 + div, h3 + div, .text-3xl + div {
break-inside: avoid;
}
h1, h2, h3, text-3xl {
break-inside: avoid;
page-break-after: avoid;
}
p, ul, li {
break-inside: avoid;
}
`;
newDocument.head.appendChild(pageStyle);
return newDocument.documentElement.outerHTML;
}
return null;
});
if (resumeContent) {
await page.setContent(resumeContent);
await page.pdf({
path: "../../static/resume.pdf",
format: "letter",
printBackground: false,
margin: {
top: "1in",
right: "1in",
bottom: "1in",
left: "1in",
},
});
} else {
console.log("No content found with id 'resume'");
}
await browser.close();
})();
Download Buttons
OK - I now have a PDF. Just one more thing to do. Let people dowload it. The challenge here was with TailwindCSS. I wanted the download button to be fixed and floating. But, I wanted it at the bottom of the page in mobile and the top in larger view. Noting I did worked till I simply created two buttons w/ different visibility parameters. Here’s the code:
<!-- Button for mobile -->
<div
class='sm:hidden fixed bottom-6 left-1/2 transform -translate-x-1/2 p-2 md:p-3 bg-zinc-600 text-white rounded-full shadow-lg hover:bg-zinc-400'
>
<a
href='/resume.pdf'
download='/brian-ross-resume.pdf'
class='flex items-center space-x-2'
>
<Icon name='mdi:download' class='w-5 h-5' />
<span class='text-xs
md:text-base'>Download PDF</span>
</a>
</div>
<!-- Button for larger screens -->
<div
class='fixed hidden z-50 sm:block top-6 right-6 p-2 md:p-3 bg-zinc-600 text-white rounded-full shadow-lg hover:bg-zinc-400'
>
<a
href='/resume.pdf'
download='brian-ross-resume.pdf'
class='flex items-center space-x-2'
>
<Icon name='mdi:download' class='w-6 h-6' />
<span class='text-xs md:text-base'>Download PDF</span>
</a>