Rewriting Strange Leaflet in Phoenix and LiveSvelte
Again? Rewriting this blog again? Yes! This blog is now a Phoenix app and using LiveSvelte for frontend components. All the power of Phoenix + all the power of Svelte == awesome.
SvelteKit
SvelteKit was great and I still say it's an interesting approach. Its foundational integration with Svelte itself is, of course, wonderful.
But check out the src/routes directory structure I had grown for Strange Leaflet
src/routes
โโโ +layout.svelte
โโโ +page.svelte
โโโ about
โ โโโ +page.svelte
โโโ api
โ โโโ blog
โ โโโ +server.js
โ โโโ featured
โ โ โโโ +server.js
โ โโโ gitblogs
โ โ โโโ +server.js
โ โโโ micro
โ โ โโโ +server.js
โ โโโ posts
โ โ โโโ +server.js
โ โโโ search
โ โโโ +server.js
โโโ blog
โ โโโ +layout.js
โ โโโ +page.js
โ โโโ +page.svelte
โ โโโ featured
โ โ โโโ +layout.js
โ โ โโโ +page.server.js
โ โ โโโ +page.svelte
โ โโโ feed[[format]]
โ โ โโโ +server.js
โ โโโ gitblogs
โ โ โโโ +layout.js
โ โ โโโ +page.server.js
โ โ โโโ +page.svelte
โ โ โโโ [post]
โ โ โ โโโ +layout.js
โ โ โ โโโ +page.js
โ โ โ โโโ +page.svelte
โ โ โโโ mdsvex.svelte
โ โโโ mdsvex.svelte
โ โโโ micro
โ โ โโโ +layout.js
โ โ โโโ +page.server.js
โ โ โโโ +page.svelte
โ โ โโโ [post]
โ โ โโโ +layout.js
โ โ โโโ +page.js
โ โ โโโ +page.svelte
โ โโโ posts
โ โโโ +layout.js
โ โโโ +page.server.js
โ โโโ +page.svelte
โ โโโ [post]
โ โ โโโ +layout.js
โ โ โโโ +page.js
โ โ โโโ +page.svelte
โ โโโ mdsvex.svelte
โโโ contact
โ โโโ +page.svelte
โโโ feed[[format]]
โ โโโ +server.js
โโโ search
โโโ +page.server.js
โโโ +page.svelte
Not terrible but I found myself editing the wrong +page.svelte file or +server.js file more than once. And any additional routes would compound the problem.
Here's the complexity around getting blog posts
const blogItems = (globbed) => {
return Object.entries(globbed)
.map(([path, meta]) => {
meta.metadata.date = new Date(meta.metadata.date);
return {
path: path
.replace(/^\/src\/lib/, "")
.replace(/\.svx$/, "")
.replace(/\/index$/, ""),
...meta,
};
})
.sort((a, b) => {
return b.metadata.date - a.metadata.date;
});
};
export const fetchPosts = async () => {
const postFiles = import.meta.glob("/src/lib/blog/posts/**/*.svx", {
eager: true,
});
return blogItems(postFiles);
};
Phoenix
Conversely this is the lib directory tree for this Phoenix app
lib
โโโ mix
โ โโโ tasks
โ โโโ strangeleaflet.blog.gen.feed.ex
โโโ strange_leaflet
โ โโโ application.ex
โ โโโ blog
โ โ โโโ blog.ex
โ โ โโโ post.ex
โ โโโ mailer.ex
โโโ strange_leaflet.ex
โโโ strange_leaflet_web
โ โโโ components
โ โ โโโ core_components.ex
โ โ โโโ layouts
โ โ โ โโโ app.html.heex
โ โ โ โโโ root.html.heex
โ โ โโโ layouts.ex
โ โโโ controllers
โ โ โโโ blog_controller.ex
โ โ โโโ blog_html
โ โ โ โโโ index.html.heex
โ โ โ โโโ pagination.html.heex
โ โ โ โโโ show.html.heex
โ โ โ โโโ tagged.html.heex
โ โ โ โโโ tags.html.heex
โ โ โโโ blog_html.ex
โ โ โโโ error_html
โ โ โ โโโ 404.html.heex
โ โ โ โโโ 500.html.heex
โ โ โโโ error_html.ex
โ โ โโโ error_json.ex
โ โ โโโ page_controller.ex
โ โ โโโ page_html
โ โ โ โโโ about.html.heex
โ โ โ โโโ homepage.html.heex
โ โ โโโ page_html.ex
โ โ โโโ widget_controller.ex
โ โ โโโ widget_html.ex
โ โโโ endpoint.ex
โ โโโ gettext.ex
โ โโโ router.ex
โ โโโ telemetry.ex
โโโ strange_leaflet_web.ex
Much easier to follow and organize concepts, at least for me. Certainly easier to wire up new routes!
get "/widgets", WidgetController, :index
get "/widgets/:id", WidgetController, :show
And because it's Elixir I can run my application and connect Livebook to that running application and interact with my code using Livebook as a wonderful REPL. Here's me figuring out the code to build up the list of tags and counts from posts.

LiveSvelte
Thanks to LiveSvelte I don't have to give up the power of Svelte components for the frontend! Everything in /widgets is currently a Svelte component. I could add LiveView components as well. I could even have Svelte components wired up to LiveView!
The navigation header you see is still a Flowbite-Svelte component. I still have my customized penguin component.
Svelte
How's the Svelte component experience? Pretty great!
I have assets/svelte and all the Svelte components are in that directory. If I want to use any npm packages such as the excellent Flowbite Svelte I simply add them in the assets directory which has a normal package.json and them they're available to use in a Svelte component.
import { Pagination } from "flowbite-svelte";
NimblePublisher
Instead of mdsvex to organize .svx (Svelte + Markdown) files as blog posts I'm using NimblePublisher from Dashbit. It serves a similar purpose but I get to use Elixir and a well thought out developer experience to organize blog posts.
Elixir school has an excellent tutorial on using NimblePublisher if you're interested.
Here's how I tell NimblePublisher where and what to find as blog posts.
use NimblePublisher,
build: Post,
from: Application.app_dir(:strange_leaflet, "priv/posts/**/*.md"),
as: :posts,
highlighters: [:makeup_elixir, :makeup_erlang, :makeup_json, :makeup_js]
And to have a sorted set of @posts?
@posts Enum.sort_by(@posts, & &1.date, {:desc, Date})
Here's how I build up my tag list.
@tags @posts
|> Enum.flat_map(& &1.tags)
|> Enum.frequencies()
|> Enum.sort_by(fn {_tag, count} -> count end, :desc)
This experience being so smooth and easy is key for me since I want to introduce a "leaflet" concept separate from blog posts.
I have a great blog writing experience, a powerful web backend, and a powerful web frontend: all in one app!