Vince Commero

A Blog-Making Blog, Part 1

April 7, 2025

While perhaps running the risk of a blog that "insists on itself," I figured I'd start off my foray into blogs and maintaining my own blog site by talking a little bit about the journey of building this site. I could've quite easily just made a Substack or a Wordpress blog, and that's indeed what many people do. However, being a software engineer, I wanted to hone my experience going through the journey of coding, building, and deploying my own website myself. Aside from the technical experience gained and the sense of accomplishment afterward, it also provides me a lot more freedom to really do whatever I want with the site in regards to functionality, layout, and aesthetic.

The Idea

As far as the architecture of the blog goes, the general I had was to store the blog articles as a collection of Markdown documents. Markdown is a simple enough format that pretty much provides all the formatting I would generally need for a blog article. These markdown files would be stored outside of the blog web app itself. The web app would then dynamically retrieve article Markdown files as needed and parse them into HTML that would be rendered on the site. This way, writing a new blog post would be as simple as just uploading a new Markdown file to wherever I decide to store the articles.

Tech Stack Decisions

As of 2025, there is no shortage of tools and frameworks in a multitude of programming languages that can be used to accomplish this, so settling on a framework for the project was a lot more involved than I expected. There's a ton of frameworks and libraries around that I could build this site with. However, a large number of those libraries proved to be either overly engineered and complex for what I was trying to do, or overly restrictive and hard to work with the further you stray from the paths and workflows they want you to follow.

Vaadin

First up as a candidate was Vaadin. Vaadin is a framework for web apps built on the Java Spring framework. Given that I have a lot of experience with Java, I wanted to give Vaadin a shot. What made Vaadin interesting for me was Vaadin Flow, Vaadin's library for building and styling UIs and webpages entirely in Java within your web app. They even have a handy app on the Vaadin website that let's you drag and drop components to build a basic structure for your site which you can then download as a starter Spring Boot project! Since I'm building this app as a solo developer, I'm mainly looking for a framework that requires minimal configuration out-of-the-box to get things looking good and "just works." Since Vaadin has a lot of functionality built-in, complete with built-in and themeable UI components, this framework looked pretty promising.

Things didn't quite go as planned though and I ran into some difficulties soon enough: first some annoyances, then more glaring problems further in. First off, Vaadin Flow's paradigm of building UIs entirely in Java classes instead of HTML and Javascript presents a learning curve that does require some investment to overcome. Fortunately, there is plenty of documentation on the Vaadin website to work with, but I was finding myself having to sink more time than I wanted into learning Vaadin Flow.

The app builder on the Vaadin website was helpful for building a base project to work with, with drag and drop components that can be styled, arranged, etc. However, the code that is generated for each page was not exactly easy to work with. For example, here's a simple concept of a blog article page (Ignore the placeholder content!):

Blog page concept in Vaadin

The generated code produced from the Vaadin builder app to produce that page looks like this:

package com.vincecommero.views.post; import com.vaadin.flow.component.Composite; import com.vaadin.flow.component.avatar.Avatar; import com.vaadin.flow.component.html.H1; import com.vaadin.flow.component.html.Hr; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment; import com.vaadin.flow.component.orderedlayout.FlexComponent.JustifyContentMode; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.tabs.Tab; import com.vaadin.flow.component.tabs.Tabs; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.theme.lumo.LumoUtility.Gap; import com.vaadin.flow.theme.lumo.LumoUtility.Padding; import mainViewPackage.components.avataritem.AvatarItem; @PageTitle("Post") @Route("post") public class PostView extends Composite<VerticalLayout> { public PostView() { VerticalLayout layoutColumn2 = new VerticalLayout(); VerticalLayout layoutColumn3 = new VerticalLayout(); HorizontalLayout layoutRow = new HorizontalLayout(); H1 h1 = new H1(); Tabs tabs = new Tabs(); VerticalLayout layoutColumn4 = new VerticalLayout(); HorizontalLayout layoutRow2 = new HorizontalLayout(); VerticalLayout layoutColumn5 = new VerticalLayout(); H1 h12 = new H1(); VerticalLayout layoutColumn6 = new VerticalLayout(); H1 h13 = new H1(); Paragraph textLarge = new Paragraph(); AvatarItem avatarItem = new AvatarItem(); HorizontalLayout layoutRow3 = new HorizontalLayout(); Span badge = new Span(); Hr hr = new Hr(); VerticalLayout layoutColumn7 = new VerticalLayout(); Paragraph textLarge2 = new Paragraph(); getContent().setWidth("100%"); getContent().getStyle().set("flex-grow", "1"); getContent().setJustifyContentMode(JustifyContentMode.START); getContent().setAlignItems(Alignment.CENTER); layoutColumn2.setWidthFull(); getContent().setFlexGrow(1.0, layoutColumn2); layoutColumn2.setPadding(false); layoutColumn2.setWidth("100%"); layoutColumn2.setMaxWidth("1140px"); layoutColumn2.getStyle().set("flex-grow", "1"); layoutColumn3.setWidthFull(); layoutColumn2.setFlexGrow(1.0, layoutColumn3); layoutColumn3.setPadding(false); layoutColumn3.setWidth("100%"); layoutColumn3.setHeight("min-content"); layoutRow.setWidthFull(); layoutColumn3.setFlexGrow(1.0, layoutRow); layoutRow.addClassName(Gap.MEDIUM); layoutRow.addClassName(Padding.MEDIUM); layoutRow.setWidth("100%"); layoutRow.getStyle().set("flex-grow", "1"); h1.setText("Vince Commero"); h1.setWidth("max-content"); layoutRow.setAlignSelf(FlexComponent.Alignment.END, tabs); tabs.setWidth("100%"); setTabsSampleData(tabs); layoutColumn4.setWidthFull(); layoutColumn2.setFlexGrow(1.0, layoutColumn4); layoutColumn4.setWidth("100%"); layoutColumn4.getStyle().set("flex-grow", "1"); layoutRow2.setWidthFull(); layoutColumn4.setFlexGrow(1.0, layoutRow2); layoutRow2.addClassName(Gap.MEDIUM); layoutRow2.setWidth("100%"); layoutRow2.setHeight("min-content"); layoutRow2.setAlignItems(Alignment.START); layoutRow2.setJustifyContentMode(JustifyContentMode.CENTER); layoutColumn5.setHeightFull(); layoutRow2.setFlexGrow(1.0, layoutColumn5); layoutColumn5.setWidth("min-content"); layoutColumn5.getStyle().set("flex-grow", "1"); layoutColumn5.setJustifyContentMode(JustifyContentMode.CENTER); layoutColumn5.setAlignItems(Alignment.END); h12.setText("Replace with Image component"); h12.setWidth("max-content"); layoutColumn6.setHeightFull(); layoutRow2.setFlexGrow(1.0, layoutColumn6); layoutColumn6.setWidth("100%"); layoutColumn6.getStyle().set("flex-grow", "1"); layoutColumn6.setJustifyContentMode(JustifyContentMode.CENTER); layoutColumn6.setAlignItems(Alignment.START); h13.setText("Post Title"); h13.setWidth("max-content"); textLarge.setText( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); textLarge.setWidth("100%"); textLarge.getStyle().set("font-size", "var(--lumo-font-size-xl)"); avatarItem.setWidth("min-content"); setAvatarItemSampleData(avatarItem); layoutRow3.setWidthFull(); layoutColumn6.setFlexGrow(1.0, layoutRow3); layoutRow3.addClassName(Gap.MEDIUM); layoutRow3.setWidth("100%"); layoutRow3.setHeight("min-content"); badge.setText("Badge"); badge.setWidth("min-content"); badge.getElement().getThemeList().add("badge"); layoutColumn7.setWidthFull(); layoutColumn4.setFlexGrow(1.0, layoutColumn7); layoutColumn7.setWidth("100%"); layoutColumn7.getStyle().set("flex-grow", "1"); textLarge2.setText( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. This is more text for my blog. Just random text that has no meaning whatsoever. I need to fill this with text to test out how this looks."); textLarge2.setWidth("100%"); textLarge2.getStyle().set("font-size", "var(--lumo-font-size-xl)"); getContent().add(layoutColumn2); layoutColumn2.add(layoutColumn3); layoutColumn3.add(layoutRow); layoutRow.add(h1); layoutRow.add(tabs); layoutColumn2.add(layoutColumn4); layoutColumn4.add(layoutRow2); layoutRow2.add(layoutColumn5); layoutColumn5.add(h12); layoutRow2.add(layoutColumn6); layoutColumn6.add(h13); layoutColumn6.add(textLarge); layoutColumn6.add(avatarItem); layoutColumn6.add(layoutRow3); layoutRow3.add(badge); layoutColumn4.add(hr); layoutColumn4.add(layoutColumn7); layoutColumn7.add(textLarge2); } private void setTabsSampleData(Tabs tabs) { tabs.add(new Tab("Dashboard")); tabs.add(new Tab("Payment")); tabs.add(new Tab("Shipping")); } private void setAvatarItemSampleData(AvatarItem avatarItem) { avatarItem.setHeading("Aria Bailey"); avatarItem.setDescription("Endocrinologist"); avatarItem.setAvatar(new Avatar("Aria Bailey")); } }

The entire page layout is crammed into one giant constructor method with generic names for each component. This was far from readable and easy to work with. Fortunately, after deciphering what component was what, I was able to break out that constructor into helper methods for each component and get some more descriptive names on them.

package com.vincecommero.views.post; import com.vaadin.flow.component.Composite; import com.vaadin.flow.component.avatar.Avatar; import com.vaadin.flow.component.html.H1; import com.vaadin.flow.component.html.Hr; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment; import com.vaadin.flow.component.orderedlayout.FlexComponent.JustifyContentMode; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.tabs.Tab; import com.vaadin.flow.component.tabs.Tabs; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.theme.lumo.LumoUtility.Gap; import com.vaadin.flow.theme.lumo.LumoUtility.Padding; import com.vincecommero.components.avataritem.AvatarItem; @PageTitle("Post") @Route(value = "post") public class PostView extends Composite<VerticalLayout> { public PostView() { VerticalLayout content = getContent(); content.setWidth("100%"); content.getStyle().set("flex-grow", "1"); content.setJustifyContentMode(JustifyContentMode.START); content.setAlignItems(Alignment.CENTER); VerticalLayout outerContainer = createOuterContainer(); content.add(outerContainer); VerticalLayout headerContainer = createHeaderContainer(); outerContainer.add(headerContainer); VerticalLayout bodyContainer = createBodyContainer(); outerContainer.add(bodyContainer); } // Component creation helper methods private VerticalLayout createOuterContainer() { VerticalLayout outerContainer = new VerticalLayout(); outerContainer.setWidthFull(); outerContainer.setPadding(false); outerContainer.setMaxWidth("1140px"); outerContainer.getStyle().set("flex-grow", "1"); return outerContainer; } private VerticalLayout createHeaderContainer() { VerticalLayout headerContainer = new VerticalLayout(); headerContainer.setWidthFull(); headerContainer.setPadding(false); headerContainer.setHeight("min-content"); HorizontalLayout headerRow = createHeaderRow(); headerContainer.add(headerRow); return headerContainer; } private HorizontalLayout createHeaderRow() { HorizontalLayout headerRow = new HorizontalLayout(); headerRow.setWidthFull(); headerRow.addClassName(Gap.MEDIUM); headerRow.addClassName(Padding.MEDIUM); headerRow.getStyle().set("flex-grow", "1"); H1 nameText = new H1("Vince Commero"); nameText.setMinWidth("max-content"); nameText.setWidth("max-content"); Tabs headerTabs = createHeaderTabs(); headerRow.add(nameText, headerTabs); headerRow.setAlignSelf(FlexComponent.Alignment.END, headerTabs); return headerRow; } private Tabs createHeaderTabs() { Tabs headerTabs = new Tabs(); headerTabs.setWidth("100%"); headerTabs.add(new Tab("Home")); headerTabs.add(new Tab("Posts")); return headerTabs; } private VerticalLayout createBodyContainer() { VerticalLayout bodyContainer = new VerticalLayout(); bodyContainer.setWidthFull(); bodyContainer.getStyle().set("flex-grow", "1"); HorizontalLayout postHeaderContainer = createPostHeaderContainer(); bodyContainer.add(postHeaderContainer); Hr hr = new Hr(); bodyContainer.add(hr); VerticalLayout postBodyContainer = createPostBodyContainer(); bodyContainer.add(postBodyContainer); return bodyContainer; } private HorizontalLayout createPostHeaderContainer() { HorizontalLayout postHeaderContainer = new HorizontalLayout(); postHeaderContainer.setWidthFull(); postHeaderContainer.addClassName(Gap.MEDIUM); postHeaderContainer.setHeight("min-content"); postHeaderContainer.setAlignItems(Alignment.START); postHeaderContainer.setJustifyContentMode(JustifyContentMode.CENTER); VerticalLayout postPhotoContainer = createPostImageContainer(); VerticalLayout postHeaderInfoContainer = createPostHeaderInfoContainer(); postHeaderContainer.add(postPhotoContainer, postHeaderInfoContainer); return postHeaderContainer; } private VerticalLayout createPostImageContainer() { VerticalLayout postImageContainer = new VerticalLayout(); postImageContainer.setHeightFull(); postImageContainer.setWidth("100%"); postImageContainer.getStyle().set("flex-grow", "1"); postImageContainer.setJustifyContentMode(JustifyContentMode.CENTER); postImageContainer.setAlignItems(Alignment.END); H1 postImage = new H1("Replace with Image component"); postImage.setWidth("max-content"); postImageContainer.add(postImage); return postImageContainer; } private VerticalLayout createPostHeaderInfoContainer() { VerticalLayout postHeaderInfoContainer = new VerticalLayout(); postHeaderInfoContainer.setHeightFull(); postHeaderInfoContainer.setWidth("100%"); postHeaderInfoContainer.getStyle().set("flex-grow", "1"); postHeaderInfoContainer.setJustifyContentMode(JustifyContentMode.CENTER); postHeaderInfoContainer.setAlignItems(Alignment.START); H1 postTitleText = new H1("Post Title"); postTitleText.setWidth("max-content"); Paragraph postTitleDescription = new Paragraph( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); postTitleDescription.setWidth("100%"); postTitleDescription.getStyle().set("font-size", "var(--lumo-font-size-xl)"); AvatarItem authorComponent = createAuthorComponent(); HorizontalLayout postTagsContainer = createPostTagsContainer(); postHeaderInfoContainer.add(postTitleText, postTitleDescription, authorComponent, postTagsContainer); return postHeaderInfoContainer; } private AvatarItem createAuthorComponent() { AvatarItem authorComponent = new AvatarItem(); authorComponent.setWidth("min-content"); authorComponent.setMinWidth("max-content"); setAvatarItemSampleData(authorComponent); return authorComponent; } private HorizontalLayout createPostTagsContainer() { HorizontalLayout postTagsContainer = new HorizontalLayout(); postTagsContainer.setWidthFull(); postTagsContainer.addClassName(Gap.MEDIUM); postTagsContainer.setHeight("min-content"); Span postTags = new Span("Badge"); postTags.setWidth("min-content"); postTags.getElement().getThemeList().add("badge"); postTagsContainer.add(postTags); return postTagsContainer; } private VerticalLayout createPostBodyContainer() { VerticalLayout postBodyContainer = new VerticalLayout(); postBodyContainer.setWidthFull(); postBodyContainer.setWidth("100%"); postBodyContainer.getStyle().set("flex-grow", "1"); Paragraph postBody = new Paragraph( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. This is more text for my blog. Just random text that has no meaning whatsoever. I need to fill this with text to test out how this looks."); postBody.setWidth("100%"); postBody.getStyle().set("font-size", "var(--lumo-font-size-xl)"); postBodyContainer.add(postBody); return postBodyContainer; } private void setAvatarItemSampleData(AvatarItem avatarItem) { avatarItem.setHeading("Vince Commero"); avatarItem.setDescription("Software Engineer"); avatarItem.setAvatar(new Avatar("Vince Commero")); } }

Not the best, but certainly more readable and organized. Personally, I think it's actually a bit of a challenge to define a page structure using something like Java classes while still having the code be readable, since they aren't really set up to display nested document structures as well XML-type documents or other such documents dedicated for that.

Diving further, the heavily abstracted nature of Vaadin Flow proved to also be rather restrictive if you wanted functionality beyond their built-in styles and components. Vaadin does have solution for those wanting greater customization and control with Hilla, a framework for implementing the frontend as a standard React app rather than the "Java only" implementation of Vaadin Flow. However, this seemed to undo the main selling points of Vaadin for me personally.

Final Thoughts

In the end, I decided to ultimately forego Vaadin for implementing the blog site. The Vaadin Flow "Java only" frontend implementation proved to not only have a learning curve, but also felt clunky to use when building page layouts. This implementation could be plenty fine for building simple interfaces that don't need much customization, like what would be used in internal applications (which is where many people say Vaadin shines). However, this system becomes tricky to use once you have deeper, more complex page structures or if you need more customized functionality in your components. The starter project builder was very nice to have for building an inital project, however the code it generated proved to be very disorganized and difficult to work with, requiring refactoring on my end. Vaadin could stand to gain by improving this editor and improving the quality of the generated projects it produces. All in all, Vaadin has its uses but was not a fit for this particular project.

Stay tuned for further articles on the development of this site, including moving on to Next.js!