thumbnail
thumbnail

SvelteKitでSitemapとRSSを作成する

updated 2021-7-26

sveltekitとは

svelteのフレームワークSapperの後継で
Webアプリケーションが作れます

sveltekit

対象

すでにsveltekitでアプリの作成が完了している方が対象です
markdownで作成したものをhtmlページとして作成している場合に使えます

Sitemap.xmlを作成する

src/routes/sitemap.xml.tsを作成して以下のように記述します。

import fs from "fs";
import path from "path";
import frontmatter from "front-matter";
import { base } from "$app/paths";
import { create } from "xmlbuilder2";

function getPosts(filePath: string) {
    const posts = fs.readdirSync(filePath)
        .filter((fileName) => !fs.lstatSync(path.resolve(filePath, fileName)).isDirectory())
        .map((fileName) => {
        const post = fs.readFileSync(path.resolve(filePath, fileName), "utf-8");

        const { attributes } = frontmatter(post);

        return attributes;
    });

    return posts;
}

export async function get() {
    const posts = getPosts("./posts");

    let xml = create({ version: "1.0", encoding: "utf-8" })
        .ele("urlset", { 
            "xmlns": "http://www.sitemaps.org/schemas/sitemap/0.9",
            "xmlns:xhtml": "http://www.w3.org/1999/xhtml",
        })
    if (Array.isArray(posts)) {
        posts.forEach((post) => {
            xml
                .ele("url")
                .ele("loc").txt(`${base}/${post.slug}`).up()
                .ele("lastmod").txt(post.updatedAt).up()
                .ele("changefreq").txt(`daily`).up();
        });
    }
    xml
        .ele("url")
        .ele("loc").txt(`${base}/`).up()
        .ele("lastmod").txt(String(new Date())).up()
        .ele("changefreq").txt(`daily`).up();
    let sitemap = xml.end({ prettyPrint: true });

    return {
        status: 200,
        body: sitemap,
    }
}

getPosts

マークダウンで作成したファイルを読み込むのに使っています
get()内で読み込む際にMarkdownのフォルダを指定してください

get

lastmodがpostのupdatedAtになっていますが、これはマークダウン内のメタデータによって変更してください
最後に編集した時刻の乗っているものです

xmlbuilder2

xmlを作成するのに便利なライブラリです
github xmlbuilder2
使い方はGithubを見た方がわかりやすいと思います。

frontmatter

マークダウンのメタデータを読み込んでくれるライブラリです
github front-matter

RSSを作成する

src/routes/rss.xml.tsを作成して以下のように記述します。

import fs from "fs";
import path from "path";
import frontmatter from "front-matter";
import { create } from "xmlbuilder2";

let basePath = "https://mekos.site";
let today = new Date().toUTCString();

function getPosts(filePath: string) {
    const posts = fs.readdirSync(filePath)
        .filter((fileName) => !fs.lstatSync(path.resolve(filePath, fileName)).isDirectory())
        .map((fileName) => {
        const post = fs.readFileSync(path.resolve(filePath, fileName), "utf-8");

        const { attributes } = frontmatter(post);

        return attributes;
    });

    return posts;
}

export async function get() {
    const posts = getPosts("./posts");

    let xml = create({ version: "1.0", encoding: "utf-8" })
        .ele("rss", { 
            "xmlns:atom": "http://www.w3.org/2005/Atom",
            "xmlns:dc": "http://purl.org/dc/elements/1.1/",
            "xmlns:content": "http://purl.org/rss/1.0/modules/content/",
            "version": "2.0"
        })
        .ele("channel")
        .ele("title").txt("Signpost by meko").up()
        .ele("link").txt(basePath + "/").up()
        .ele("language").txt("ja").up()
        .ele("copyright").txt("Copyright 2020-2021 meko").up()
        .ele("description").txt("engineer blog").up()
        .ele("lastBuildDate").txt(today).up()
        .ele("docs").txt("https://validator.w3.org/feed/docs/rss2.html").up()
        .ele("atom:link", { "href": `${basePath}/feed.xml`, rel: "self", type: "application/rss+xml" }).up();
    if (Array.isArray(posts)) {
        posts.forEach((post) => {
            xml
                .ele("item")
                .ele("title").txt(`${post.title}`).up()
                .ele("dc:creator").txt(`${post.author}`).up()
                .ele("link").txt(`${basePath}/${post.slug}/`).up()
                .ele("guid", { isPermaLink: "true" }).txt(`${basePath}/${post.slug}/`).up()
                .ele("pubDate").txt(new Date(post.updatedAt).toUTCString()).up()
                .ele("enclosure", { url: `${basePath}/${post.thumbnail}`, length: 0, type: "image/png"}).up()
                .ele("description").txt(`<![CDATA[${post.description}<img src="${basePath}/${post.thumbnail}">]]>`).up();
        });
    }
    let rss = xml.end({ prettyPrint: true });

    return {
        status: 200,
        body: rss,
    }
}

getPosts

マークダウンで作成したファイルを読み込むのに使っています
get()内で読み込む際にMarkdownのフォルダを指定してください

get

basePathはサイトのベースのURLです

lastmodがpostのupdatedAtになっていますが、これはマークダウン内のメタデータによって変更してください
最後に編集した時刻の乗っているものです

slugはページまでのパスです

forEach内の項目で無いものは削除しても大丈夫です

xmlbuilder2

xmlを作成するのに便利なライブラリです
github xmlbuilder2
使い方はGithubを見た方がわかりやすいと思います。

frontmatter

マークダウンのメタデータを読み込んでくれるライブラリです
github front-matter

Validaterでチェック

Sitemap用

my sitemap generator

RSS用

W3C Validater

Index.svelte

src/routes/index.svelte内で先ほど作成したページを読み込みます

<script context="module">
    ...
    export async function load({ fetch }) {
        // sitemap & rss
        await fetch(`${base}/sitemap.xml`);
        await fetch(`${base}/rss.xml`);
    }
</script>

これで完了です。

上手くいかない時

RSSにしていない

svelte.config.js内で設定できます

...
const config = {
    
    kit: {
		// hydrate the <div id="svelte"> element in src/app.html
        ...
		ssr: true,
	},
};

export default config;