After seeing a few of the recent announcements in the Dart and Flutter ecosystem that have been released, I decided it would be a good idea to switch over to Jaspr and move away from the original, weird implementation that was a haphazard mix of bash scripts and Flutter that had a bunch of problems.
Problems to Solve#
SEO was probably the biggest external problem. Since Flutter is meant for making web-apps and not web-sites, it's difficult to impossible for web crawlers to extract valuable information from the contents of the site for proper indexing. Originally I got around this by also having a bash script and some templating generate a static version of the site that loads in the case that the reader of the website isn't capable of rendering and executing JavaScript. If the reader is capable of rendering JavaScript, then the static view of the website ends up getting replaced in the browser by the Flutter version of the site, which admittedly had a pretty nice user experience.
Custom Components to maintain was another problem with the original static/Flutter combo site. If I really wanted to have a static version of the site fairly equivalent in experience to the Flutter version of the site, I'd have had to maintain a library of custom compatibility components that worked for both. Ultimately, switching to a single implementation that is static and still gives a good experience with enough room to create implementations of needed things on an ad hoc basis, but only once, is way more maintainable in the long term.
Custom Scripts for doing things like building both versions of the site was also a problem. As the site would have become more complex over time, maintaining those scripts in a way compatible with both Flutter and the static templates would have eventually ballooned out of control.
Original Intent and Why Jaspr#
The original intent was that I could build a static site using Flutter. My mistake was a misunderstanding of Flutter's purpose, putting me down the wrong path. Like I mention above, Flutter is meant for making web-apps, not web-sites. The difference between the two is the purpose: web-apps are meant to provide functionality, web-sites are meant to provide (primarily) textual content. Due to how early I was into my Flutter and Dart journey (and I still am quite early on that journey at the time of this writing, even), I didn't realize at the time that there wasn't a static site generator that could use Flutter components, and I didn't know or think to do deeper research at the time on the Dart ecosystem to discover the preferred Dart packages for static site creation. I was caught up in my success of making Flutter apps and had a blind spot as a result.
Ten days ago I watched this video from the Flutter Official
YouTube channel on building websites with Dart and Jaspr and realized this was the opportunity I needed
to rebuild my personal website to better match my original intent of making a static website. Static websites
need to have complete HTML and JavaScript at build time, and static website generators like
jaspr provide
that capability. My original site from many years ago was built using Hugo, which is a golang-based static
site generator. Golang is fine as far as languages go, but it's not a very inspiring language, for me at least.
Creating custom templates for Hugo (or many other static site generators, for that matter) ultimately requires
writing a bunch of CSS, HTML, and JavaScript. All good skills to have, but again, not very inspiring either.
Being able to create Dart components using classes that provide much of the CSS, HTML, and JavaScript for me
sounds way more fun. To top it off, I don't need to learn any special templating language if I don't want to.
I do have access to Mustache, so I have the ability to drop into a templating language within my content if I
need to, but the templating language doesn't have to drive my whole app.
Static site generators like Jekyll and Hugo have you write your whole site in JavaScript, HTML, and CSS with the template language integrated so that your content can be inlined into the templated sections of the site. Then the static site generator is essentially just a script that does the inlining of your content into the templated sections of your site.
jaspr does things a little differently. All your content still lives within whatever file format you might
select (Markdown being especially common, and the one I use), but you don't have to write HTML, CSS, and
JavaScript templates if you don't want to. jaspr and its additional libraries such as
jaspr_content
provide Dart classes for declaring HTML, CSS, and JavaScript using the Dart language. I can still create
custom components for HTML, CSS, and JavaScript using a templated language into Dart classes, more similarly
to Next.js with its React components. The big benefit of this approach is that you have reusable components
that let you build your whole website declaratively using a language that's easy to work with. You only
have to maintain your mental context on how a particular piece of HTML, CSS, or JavaScript works while you're
making a particular component.
Example of Benefits#
An example from this very site that demonstrates this is the social links in the top-right corner of every
page. I was able to create a fairly generic component that is reusable. I probably will want to extend this
a bit in the future (for instance, breaking out the fontSize and icon dimensions to function arguments with
sensible defaults), but for now this is sufficient.
class ProfileButton extends StatelessComponent {
const ProfileButton({
required this.platform,
required this.username,
required this.iconPath,
required this.profileUrl,
this.ariaLabel,
super.key,
});
final String platform;
final String username;
final String iconPath;
final String profileUrl;
final String? ariaLabel;
@override
Component build(BuildContext context) {
final defaultLabel = '$platform profile for $username';
final label = ariaLabel ?? defaultLabel;
return a(
href: profileUrl,
classes: 'profile-button',
[
img(
src: iconPath,
alt: '$platform logo',
width: 24,
height: 24,
classes: 'profile-button__icon',
),
span(
classes: 'profile-button__text',
[.text(username)],
),
],
attributes: {
'target': '_blank',
'rel': 'noopener noreferrer',
'aria-label': label,
},
);
}
@css
static List<StyleRule> get styles => [
css('.profile-button').styles(
display: Display.inlineFlex,
alignItems: AlignItems.center,
padding: .all(0.5.rem),
margin: .only(right: 0.5.rem),
color: ContentColors.links,
textDecoration: TextDecoration.none,
fontSize: 0.6.em,
),
css('.profile-button__icon').styles(
width: 12.px,
height: 12.px,
display: Display.block,
margin: .only(right: 0.5.rem),
),
];
}
To Do#
So at this point the website has been fully switched over to jaspr, but I still have a bunch of changes
that I'd like to make so that it more closely matches the original Flutter experience and also better
takes advantage of what jaspr and jaspr_content bring to the table.
- Only show the three most recent Projects and Blogs in the sidebar, and make the last link from each one a link to a complete table of contents.
- Make the Projects and Blog landing pages have the 10 most recent posts in each of those categories as "cards" or "tiles".
- Experiment with Flutter embeddings. Like maybe I can make some weird or pointless little web-app to live inside a page.
- Figure out how to make more of the YAML frontmatter visible in each posting in a reasonable fashion.
- Improve SEO beyond what I currently have going on.
- Make a page that visitors can check out to filter Blogs and Projects by tag.
- RSS Feed, because why not?
No doubt I'll come up with some more ideas as well.