Skip to content

Conversation

josecelano
Copy link

Relates to: torrust/torrust-website#141

Hi @grmbyrn as we discussed in the last weekly meeting @da2ce7 and I agreed on trying to put the metadata and the page together instead of having a global array for the metadata. That would make it easier to add new pages.

ChatGPT has implemented this proof of concept. The code is very bad (a lot of duplication and other things) but that the main idea.

This is a sample page src/routes/blog/how-to-contribute-to-this-site/+page.ts

import { getAllPostsMetadata, type PostMetadata } from "$lib/data/postMetadata";

export const _meta: PostMetadata = {
	title: 'How To Contribute To This Site',
	slug: 'how-to-contribute-to-this-site',
	contributor: 'Jose Celano',
	contributorSlug: 'jose-celano',
	date: '2023-04-22T21:55:15.361Z',
	coverImage: '/images/posts/sample-post.jpg',
	excerpt: 'How to manage existing blog posts and create new ones on this site.',
	tags: ['Documentation']
};

export async function load() {
	const allMetadata: PostMetadata[] = await getAllPostsMetadata();
	return { _meta, allMetadata };
}

As you can see now the metadata is inside the +page.ts. We can use a metadata.ts or metadata.json files if you prefer to keep it decoupled from Svelte.

The code can be improved significantly. I guess the most obvious improvement is to use a cache for the getAllPostsMetadata. The content is static. We only need to do it once.

We can only try to do it at build time for production. We can generate the global json and put it somewhere. And then we just need to load that file. I guess that can be a later improvement. We don't have many blog posts yet.

@grmbyrn
Copy link
Owner

grmbyrn commented Feb 14, 2025

Relates to: torrust/torrust-website#141

Hi @grmbyrn as we discussed in the last weekly meeting @da2ce7 and I agreed on trying to put the metadata and the page together instead of having a global array for the metadata. That would make it easier to add new pages.

ChatGPT has implemented this proof of concept. The code is very bad (a lot of duplication and other things) but that the main idea.

This is a sample page src/routes/blog/how-to-contribute-to-this-site/+page.ts

import { getAllPostsMetadata, type PostMetadata } from "$lib/data/postMetadata";

export const _meta: PostMetadata = {
	title: 'How To Contribute To This Site',
	slug: 'how-to-contribute-to-this-site',
	contributor: 'Jose Celano',
	contributorSlug: 'jose-celano',
	date: '2023-04-22T21:55:15.361Z',
	coverImage: '/images/posts/sample-post.jpg',
	excerpt: 'How to manage existing blog posts and create new ones on this site.',
	tags: ['Documentation']
};

export async function load() {
	const allMetadata: PostMetadata[] = await getAllPostsMetadata();
	return { _meta, allMetadata };
}

As you can see now the metadata is inside the +page.ts. We can use a metadata.ts or metadata.json files if you prefer to keep it decoupled from Svelte.

The code can be improved significantly. I guess the most obvious improvement is to use a cache for the getAllPostsMetadata. The content is static. We only need to do it once.

We can only try to do it at build time for production. We can generate the global json and put it somewhere. And then we just need to load that file. I guess that can be a later improvement. We don't have many blog posts yet.

Hi @josecelano I've implemented changes in 737337f I could set it up with a metadata.ts file for each blog post which is accessed in +page.ts and made available in +page.svelte and this works fine, but the problem is that from what I can see, this metadata isn't available in other parts of the website such as in latest posts on home page and in the PrevNextPost component. So to make it work as we have it, I did the following:

Summary

  • Implemented a script (scripts/generateMetadata.ts) to generate static/blogMetadata.json from routes/blog/**/metadata.ts files.
  • Updated routes/(home)/+page.server.ts to load and sort blog metadata for display on the homepage.
  • Modified routes/(home)/+page.svelte to show the latest blog posts dynamically.
  • Updated routes/blog/blog-post/+page.server.ts to retrieve individual blog post metadata based on the URL slug.
  • Modified routes/(blog)/blog-post/+page.svelte to use the loaded metadata for rendering blog content.

How It Works

  1. Metadata Generation (scripts/generateMetadata.ts)
  • Scans routes/blog/**/metadata.ts files.
  • Extracts metadata and saves it to static/blogMetadata.json.
  1. Making Metadata Available
  • +page.server.ts in (home) loads all posts from blogMetadata.json and sorts them.
  • +page.svelte in (home) displays the latest blog posts.
  • +page.server.ts in (blog)/blog-post/ finds the correct blog post using the slug.
  • +page.svelte in (blog)/blog-post/ uses this data for rendering.

Important

  • Running "build": "tsx scripts/generateMetadata.ts && vite build" in package.json ensures metadata of new blog post is added.

@josecelano
Copy link
Author

Hi @grmbyrn, in this function:

export const load: PageServerLoad = async ({ url }) => {
	const slug = url.pathname.split('/').filter(Boolean).pop();

	if (!slug) {
		throw new Error('Slug could not be determined.');
	}

	const metadataPath = path.resolve('static/blogMetadata.json');

	if (!fs.existsSync(metadataPath)) {
		throw new Error("Metadata file not found. Run 'npm run generate-metadata'.");
	}

	const metadata: BlogMetadata[] = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));

	const currentPost = metadata.find((post) => post.slug === slug);

	if (!currentPost) {
		throw new Error(`Post not found: ${slug}`);
	}

	return { currentPost, allPosts: metadata };
};

I would extract a global state to avoid loading the whole metadata file each time you load a blog post. Maybe you can cache metadata in memory instead of in a static file? I mean, you can continue using the static file but cache it in memory the first time you load from the backend.

If that's not possible for some reason you can extract the a function to remove that duplicate code in all pages.

@grmbyrn
Copy link
Owner

grmbyrn commented Feb 17, 2025

Hi @grmbyrn, in this function:

export const load: PageServerLoad = async ({ url }) => {
	const slug = url.pathname.split('/').filter(Boolean).pop();

	if (!slug) {
		throw new Error('Slug could not be determined.');
	}

	const metadataPath = path.resolve('static/blogMetadata.json');

	if (!fs.existsSync(metadataPath)) {
		throw new Error("Metadata file not found. Run 'npm run generate-metadata'.");
	}

	const metadata: BlogMetadata[] = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));

	const currentPost = metadata.find((post) => post.slug === slug);

	if (!currentPost) {
		throw new Error(`Post not found: ${slug}`);
	}

	return { currentPost, allPosts: metadata };
};

I would extract a global state to avoid loading the whole metadata file each time you load a blog post. Maybe you can cache metadata in memory instead of in a static file? I mean, you can continue using the static file but cache it in memory the first time you load from the backend.

If that's not possible for some reason you can extract the a function to remove that duplicate code in all pages.

Hi @josecelano in this commit 66449bd I updated the implementation to cache metadata in memory while still using the static file as the source. Now, the metadata is loaded once and stored globally, preventing unnecessary file reads on each request.

Additionally, I’ve extracted the metadata loading logic into a separate function (getMetadata) to remove duplicate code across pages. This keeps the load function cleaner and more maintainable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants