1. Project management

This week I worked on defining my final project idea and learning about static site generators (SSGs). The initial template provided by Fab Academy was converted to use the Astro web framework.

Details of my proposed final project can be found on the Final Project page.

The Student Agreement is here.

Preparatory work

Git and Fablab repository

I followed the version control recitation video to get git up and running and downloaded the Fab Academy initial student website template.

I ran into the same difficulty as the recitation instructor because of pre-existing ssh keys, so I also had to create a ~/.ssh/config file which included the following to let git know which ssh key to use:

Host gitlab.fabcloud.org User git IdentityFile ~/.ssh/id_ed25519_fabcloud

Initial website comments

I noticed that the website was set up to have a menu bar with links to all pages. This menu is duplicated on all pages and would need to be updated on all pages whenever a page is added to or removed from the website. Therefore, I wanted to change the implementation of the website so that the menu could be updated in just one place. This also applied to the footer which appears at the bottom of all the pages.

There are a number of ways in which this refactoring could be accomplished. I have used Adobe Dreamweaver in the past, which supports page templates for websites. The downside of this approach is that each change to the page template results in updates to all pages, so all pages need to be re-uploaded to the web server after each page template change. For large websites with images and other non-html files scattered throughout the directory structure, it gets unwieldy to pickout out which files to upload to the server, if one is trying to minimize re-uploading large non-html file content, unless one is using something like git.

Another approach is to use javascript to generate the menus and footer and to include the javascript file in each page of the website. An issue with this approach is that modern web servers and browsers like to cache included files, so they will not always request or send over to the web client the newest javascript file. There are hacks to get around this issue.

You can also build website that uses a templating engine on the backend. I've used django in the past, and it works well, but each page request results in a just-in-time creation of the page, which means that for some large, data-heavy pages, they can be a bit slow to load. In addition, django requires that you have access to a server and database.

For this website, I thought I would try something new (to me), which is to use a static site generator. Static site generators are frameworks that let you define web content. The framework then generates the website pages. Nowadays, there are many web hosting services that support static site generators, so they will deliver the latest version of the website each time you ask the site to be rebuilt (usually automatic after a git push).

I read about static site generators in general and about different possibilities. For flexibility, I wanted one that would allow me to continue working in html and not just Markdown. I selected Astro because it was supposedly designed to be easy to use for people who are familiar with HTML and JavaScript.

Visual Studio Code

For code editing, I downloaded Visual Studio Code and also installed the Astro extension.

For customizations, I enabled auto-save by searching for save in the settings and updated the Auto Save option to afterDelay, as shown below:

auto save settings in visual studio code

I also enabled line-wrapping by searching for wrap in the settings search bar and updated the Word Wrap option to on and the Wrapping Indent option to indent, as shown below:

word wrap settings in visual studio code

Porting to Astro

For preparation, I went through the first 4 Units of the Astro tutorial, but skipped the sections involving GitHub and Netlify because I only wanted to try it out locally, meaning on my own machine. I also found the official guide for deploying Astro Sites to GitHub Pages and looked at their minimal Astro project template on github. This blog post on How to publish your Astro Site with GitLab Pages was also super helpful. Then, it was time to start porting the Fab Academy initial student template.

Installing Node.js

Astro requires Node.js to be installed. To do this, go to the Node.js page and download the installer and run it. You can pick either the LTS or current version — both will work fine. If you have successfully installed Node.js, then you should be able to check which version of Node.js you have installed by typing node -v at a command prompt.

Note: If you think you may want to do more Node.js development, you may want to install the Node Version Manager (NVM), which lets you specify different versions of Node.js for each of your projects. Doing so is outside of the scope of these instructions.

Astro Installation Options

As part of the Node.js installation, the Node Package Manager (NPM) is installed. We will use npm to install Astro. There are two ways to install Astro, either manually or using the Astro Command Line Interface (CLI), which provides a setup wizard. If you use the Astro CLI, it will create a project directory for you with scaffolding for the options that you select in the wizard.

For these instructions, we will be setting up Astro manually, to better understand each step that the wizard does for us.

Navigate to website directory

To start, in a terminal, navigate to your Fab Academy website directory. This is the directory that is your first_name-last_name. Assuming you are using a unix-based system (or unix-style tools on Windows), you may have to use the cd (change directory) command a few times to get there. To use the command, type cd name_of_directory_to_go_into. To list the contents of the current directory, so that you know what your options are, use the ls (list) command. Once you are in your website directory, you should see the following when you list the contents of that directory:

fname-lname % ls README.md public

Note: Different shells use different prompt symbols, so you may see a $ instead of a % or something else to indicate where you can enter a command. Also, the words directory and folder refer to the same thing.

Create package.json file

Now that we are in our website's base directory, we need to create a package.json file. To do this, run the command npm init --yes.

fname-lname % npm init --yes

Using Visual Studio Code (or your code editor of choice), open up your base website folder and then open up the package.json file that was generated. Put your name in for the author and replace the default ISC license with the one that Fab Academy uses, which is the Creative Commons Attribution Non Commercial license.

"author": "Your name goes here", "license": "Creative Commons Attribution Non Commercial"

Also delete the test entry in the scripts section and replace it as shown below:

"scripts": {   "dev": "astro dev", "start": "astro dev", "build": "astro build", "preview": "astro preview" },

If you don't have auto-save, now is a good time to save the file and close it.

To install Astro, run npm install astro

fname-lname % npm install astro

Once it is done, take a look at what was installed. You should find a node_modules directory. In addition, the installation should have modifiled package.json to include Astro as a dependency and created a package-lock.json file. Open up the package.json file and you should see something like what's below near the end of the file (your version number may be different):

"dependencies": { "astro": "^4.3.5" }

Adding .gitignore

If you take a look at the node_modules directory size, you will find that it is not small! Mine is 133.3 MB! That would really hurt on the size tracker! The good news is that the node_modules directory does not need to be uploaded to Git Pages. To do this, we will need to add a .gitignore file.

At the top-level of your website (at the same level as the README.md), create a new file named .gitignore (be sure the filename begins with a period). Copy the contents of the .gitignore file from the Astro project template on github. If you are using MacOS, include the lines that are MacOS-specific. My finished file looks like this (deleted the yarn and pnpm logs, since I am only using npm):

# build output public/ # dependencies node_modules/ # logs npm-debug.log* # environment variables .env .env.production # astro .astro/ # macOS-specific files .DS_Store

Then, add the .gitignore file to your git repository online as follows:

fname-lname % git add .gitignore fname-lname % git commit -m ".gitignore for Node files" fname-lname % git push

Creating Astro project structure

Create a src folder (for source code) at the same level as the public folder. Inside the source folder, create the following folders: components, layouts, pages, scripts, styles. This is what it looks like in Visual Studio Code once that is completed:

astro folder layout

Creating Astro configuration files

It's a good idea to create the default Astro configuration files even if you won't be doing customizations. Some systems require them to exist for Astro to work properly.

At the top-level (the same level as package.json), create a new file named astro.config.mjs and put in the following content:

import { defineConfig } from 'astro/config'; export default defineConfig({ // copy the URL of your Fab Academy website and put it here site: 'https://fabacademy.org/2024/labs/lab-name/students/fname-lname/', // GitLab Pages requires generated pages to be in a directory named "public" outDir: 'public', // by default, Astro uses the "public" directory for static resources (e.g., images) // since we are using GitLab Pages, and the "public" directory is already being used, // we need to tell Astro where the static resources are located publicDir: 'static', // allow URLs to match, whether or not there is a trailing slash trailingSlash: 'ignore', });

You may refer to https://astro.build/config for the list of configuration options.

Again at the top-level, create a new file named tsconfig.json and put in the following content:

{ "extends": "astro/tsconfigs/base" }

This specifies which TypeScript template to use, if you write TypeScript code (which we will not do here). For more information on the three options, see the template configurations.

Finally, in the src directory, create a new file named env.d.ts and put in the following content:

The Astro TypeScript templates use this file.

In the future, if you start a project using the Astro CLI, it will create these configuration files and also some of the project structure for you.

The first page

Astro pages are created in the src/pages directory. Each .astro file created represents one page. The filename (before the file extension) will be the URL slug. Create an index.astro file and populate it as shown below:

--- --- <p>Hello!</p>

The code fence, the space between the two --- lines at the beginning of the file, indicate where JavaScript code can be placed that will be used in the HTML template below the code fence.

Viewing pages in dev mode

Let's take a look at this first page that we've created. In your terminal, run npm run dev.

fname-lname % npm run dev

The dev server will start and a message will tell you where to find the page on your machine. The default is http://localhost:4321/. Paste this into your web browser address bar to see the index page. It should just say "Hello!".

Note: Usually you will keep the dev server running as you develop, but if you ever want to quit it, hit Control-C.

Porting the site layout

In the src/layouts directory, create a file named BaseLayout.astro. Open up the public/index.html file and copy everything in it into the BaseLayout.astro file.

First we will enable the layout to accept a different value for the page title for each page. To do this, add the following to the top of the BaseLayout.astro page (before all of the HTML content) which tells the layout that it will be receiving the pageTitle value as an attribute from the pages that use this layout:

--- const { pageTitle } = Astro.props; ---

Update the title tag as shown below to use the value of the pageTitle attribute:

<title>{ pageTitle }</title>

The spaces between the attribute name and the curly braces are optional. You could also have written it as {pageTitle}.

Update index.astro to use the layout and specify a page title for this current page. Replace the content in index.astro with the following:

--- import BaseLayout from '../layouts/BaseLayout.astro'; const currentPageTitle = "Your Name - Fab Academy"; --- <BaseLayout pageTitle={ currentPageTitle }> <!-- page specific content goes here! --> </BaseLayout>

In the code fence area, we are specifying where we are getting the layout from and naming it BaseLayout. We also specify a value for a currentPageTitle variable (be sure to insert your name for Your Name).

In the template area (below the code fence), we are specifying that we are using the BaseLayout component that was imported and that we are assigning the pageTitle attribute in the layout the value of currentPageTitle.

Go to the BaseLayout.astro page and find the <div class="content"> tag. This tag contains all of the page specific content. It ends right before the <footer> tag. Copy and cut the entire content that is in this tag and paste it in the index.astro file in place of <!-- page specific content goes here! -->.

Go back to BaseLayout.astro and add a <slot /> element to the <content> tag. It should look like this:

<div class="content"> <slot /> </div>

When you refresh your astro index page in the browser, it should now show the index page content and the page title should show in your browser's page title area (usually the tab name).

initial astro index page

Use the stylesheet

The index page is using default browser styles because it can't find the style.css file from the initial template.

In your filesystem, move the public/style.css file (i.e., the style.css file in the public directory) into the src/styles directory.

Then update the code fence area in the BaseLayout.astro file to include the style.css file as follows:

--- import '../styles/style.css'; const { pageTitle } = Astro.props; ---

Clean up the file by deleting the following line: <link rel="stylesheet" href="style.css">. When you refresh the index page, you should now see something like this:

astro index page with stylesheet

Create a NavBar component

One of the premises of Astro is that you can create components that can be reused in many pages. We'll move the navigation bar out from the layout into a component so that if different parts of the website use different layouts, they can still share a common navigation bar.

Create a NavBar.astro file in the components directory.

Copy and cut the entire <div class="navbar"> tag from BaseLayout.astro and paste it into NavBar.astro. The default Astro configuration creates URLs that do not end in .html, so we will need to update the links in the NavBar.astro file. All links in components start with a slash and are specified relative to the base directory. So, update the navigation bar links as shown below:

<div class="navbar"> <div class="navbar-inner"> <a href="/">Weekly Assignments</a> <a href="/final-project">Final Project</a> <a href="/about">About me</a> </div> </div>

Note: In many tutorials, you'll see the links end in /, for example href="/about/". Whether a trailing slash is required or not is a configuration option that can be set in the astro.config.mjs file.

Update BaseLayout.astro to import the navigation bar component:

--- import NavBar from '../components/NavBar.astro'; import '../styles/style.css'; const { pageTitle } = Astro.props; ---

Then update the <body> tag to use the NavBar

<body> <NavBar></NavBar> <div class="content"> <slot /> </div>

When you refresh the index page, it should still look the same.

As an exercise, see if you can create a footer component from the <footer> tag and include it in the layout.

Porting the rest of the initial student template

The initial student template includes four more pages: 404.html, about.html, final-project.html, and assignments/week01.html.

In the src/pages directory, create new files 404.astro, about.astro, and final-project.astro.

In the src/pages directory, create a new assignments directory. In the assignments directory, create a new file week01.astro.

Put the following content inside 404.astro:

--- import BaseLayout from '../layouts/BaseLayout.astro'; const currentPageTitle = "Your Name - 404"; --- <BaseLayout pageTitle={ currentPageTitle }> <h1>404 - Page not found</h1> <p>Sorry the URL you typed does not exist, please use my navigation bar up top to browse my website.</p> </BaseLayout>

Put the following content inside about.astro:

--- import BaseLayout from '../layouts/BaseLayout.astro'; const currentPageTitle = "Your Name - About Me"; --- <BaseLayout pageTitle={ currentPageTitle }> <h1>About me</h1> </BaseLayout>

Put the following content inside final-project.astro:

--- import BaseLayout from '../layouts/BaseLayout.astro'; const currentPageTitle = "Your Name - Final Project"; --- <BaseLayout pageTitle={ currentPageTitle }> <h1>Final Project</h1> </BaseLayout>

Put the following content inside week01.astro:

--- import BaseLayout from '../../layouts/BaseLayout.astro'; const currentPageTitle = "Your Name - Week 1"; --- <BaseLayout pageTitle={ currentPageTitle }> <h1>1. Project management</h1> </BaseLayout>

Note that the path to BaseLayout.astro includes an extra ../ (meaning go up one level) because this file is in a sub-directory from the top-level directory.

Update the link to the week 1 assignment in index.astro as shown below. Remember to use relative links between pages.

<li><a href="assignments/week01">week 1. Project management</a></li>

Warning! By default, Astro creates a directory named after the page with an index.html file inside for each page. So, when navigating up the directory tree, you may need an extra ../ to get out the directory the page is in when the page is deployed!

Now refresh your index page and test the menu links as well as the link to the week 1 assignment page. If you enter a non-existent URL in the web browser (e.g., http://localhost:4321/asdf), it should show your 404 page.

Creating a custom link element

It turns out that Astro expects to be deployed to the top-level of a domain. However, our student websites are far down the directory structure from fabacademy.org. This causes problems for our menu links, as Astro appends them to the top-level domain. To fix this, we will create a custom link component that we will use in our navigation bar. Inspiration for this strategy came from this blog post (after hours searching for a solution).

Create a new file NavLink.astro in the components directory and paste the following content into it:

--- const productionUrlBase = 'https://fabacademy.org/2024/labs/lab-name/students/fname-lname'; const { href, ...rest } = Astro.props; var newHref = href; if (href.startsWith('/') && (Astro.url.host == 'fabacademy.org')) { var newHref = productionUrlBase+href; } --- <a href={ newHref } {...rest} > <slot / > </a >

Be sure to replace the productionUrlBase with the URL to your website! In an ideal world, this would not be set here but would be read from the environment, or something like that...a problem for another time.

In this component, if the href value starts with a slash and the site is on fabacademy.org, it will add the productionUrlBase to the beginning of the href value. The component creates an <a> tag that is the same as what was passed to it, except with this modification.

Modify NavBar.astro to use new component as shown below:

--- import NavLink from './NavLink.astro'; --- <div class="navbar"> <div class="navbar-inner"> <NavLink href="/">Weekly Assignments</NavLink> <NavLink href="/final-project">Final Project</NavLink> <NavLink href="/about">About me</NavLink> </div> </div>

Test the menu links again to be sure that it all works.

Deploying to Git Pages

To deploy the Astro site, we'll have to add the Astro configuration files and the entire src directory to our github repository. We will also need to update our .gitlab-ci.yml file to trigger the astro build.

Updating .gitlab-ci.yml

Update .gitlab-ci.yml with the content below:

image: node:latest before_script: - npm ci pages: stage: deploy script: - echo "The site will be deployed to $CI_PAGES_URL"   - npm run build artifacts: paths: - public rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Updating the git repository

Run the following commands to move the current astro project to the git repository online.

fname-lname % git add .gitlab-ci.yml fname-lname % git -m "configured for Astro" fname-lname % git add astro.config.mjs package-lock.json package.json tsconfig.json fname-lname % git commit -m "Astro configuration files" fname-lname % git add src fname-lname % git commit -m "Astro version of initial student site with page placeholders" fname-lname % git push

If you try to visit your page, you will not reach it initially since index.html still exists. You can type index after your default URL to get to the Astro implementation to test it. If you have any image files, please see the notes in image support further below.

Removing the public directory

We've configured Astro to build pages into the public directory that Git Pages serves from, so the existing public directory in our git repository is in the way. Rename the public directory on your machine to something else, like public-old. Then run the following commands to delete the directory from your online repository:

fname-lname % git rm -r public fname-lname % git commit -a -m "removed public folder" fname-lname % git push

When you are finished copying information from the files in the public-old directory, you may delete it locally.

Image and file support

If you have images or other static files (e.g., PDF files) to include, these need to be put in a static directory created in the top-level (this was the directory name specified in our astro.config.mjs file). When Astro builds the website into the public directory, it will copy the contents of the static directory into the public directory. You can see how this works by running npm run build. If you look at the generated files, you'll see that for each page_name.astro page, there is a page_name directory created and an index.html page created within.

To minimize the number of directories created in the build output, you can have your static directory structure mimic the page structure. So, for example, you can create a static/assignments/week01 directory to hold the images and other files linked in your week01.astro file. In the production output, the public/assignments/week01/index.html file will be in the same directory as the images, so the relative link to those images will not need any path information. However, during testing under npm run dev these links will not work. To check if your images links work, generate the build version using npm run build and go to the public directory to find the generated file and test that in a web browser.

Astro expects all links to be specified starting with a slash, meaning relative to the base directory. However, we can't do that since we aren't deploying to the base-level of the web server. We did a hack earlier to support appending the full URL for navigation links. I suggest using relative links for links within pages, but the link needed for testing under dev mode is different from the one in build mode, which is why I suggest testing pages in a combination of the dev server and the build output. To fix this problem, a similar custom link element could be used, but hasn't been implemented yet.

Next steps

At this point, you can go through the website and update the content for each page, copying information from the pages in the public-old directory as needed. Site-wide styles go in the styles/style.css page.

Reflection

The basics of Astro are pretty straightforward. The biggest chunk of time was trying to piece together how to put the site on Git Pages and get it to work. It took a lot of searching to find someone that explained how to configure the .gitlab-ci.yml file for Astro. It's pretty simple in hindsight, but was difficult to find. Once I had the site up and found out that the navigation links didn't work, it took even longer to resolve that problem. The internet is full of posts complaining about not being able to run Astro sites deployed in sub-directories, and I didn't find a solution offered anywhere. However, I was finally able to come up with a workaround, but then discovered the discrepancy for relative links within pages. Given that, Astro isn't very user-friendly for the Fab Academy context, so I wouldn't recommend it. Writing the documentation above also took a very, very long time!