Collection of SEO utilities like sitemap, robots.txt, etc. for a Remix Application

Overview

Remix SEO

Collection of SEO utilities like sitemap, robots.txt, etc. for a Remix

Features

  • Generate Sitemap
  • Generate Robots.txt

Installation

To use it, install it from npm (or yarn):

npm install @balavishnuvj/remix-seo

Usage

For all miscellaneous routes in root like /robots.txt, /sitemap.xml. We can create a single function to handle all of them instead polluting our routes folder.

For that, lets create a file called otherRootRoutes.server.ts (file could be anything, make sure it is import only in server by ending with.server.{ts|js})

// otherRootRoutes.server.ts

import { EntryContext } from "remix";

type Handler = (
  request: Request,
  remixContext: EntryContext
) => Promise<Response | null> | null;

export const otherRootRoutes: Record<string, Handler> = {};

export const otherRootRouteHandlers: Array<Handler> = [
  ...Object.entries(otherRootRoutes).map(([path, handler]) => {
    return (request: Request, remixContext: EntryContext) => {
      if (new URL(request.url).pathname !== path) return null;
      return handler(request, remixContext);
    };
  }),
];

and import this file in your entry.server.tsx

import { renderToString } from "react-dom/server";
import { RemixServer } from "remix";
import type { EntryContext } from "remix";
+import { otherRootRouteHandlers } from "./otherRootRoutes.server";

export default async function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext
) {
+  for (const handler of otherRootRouteHandlers) {
+    const otherRouteResponse = await handler(request, remixContext);
+    if (otherRouteResponse) return otherRouteResponse;
+  }
  let markup = renderToString(
    <RemixServer context={remixContext} url={request.url} />
  );

  responseHeaders.set("Content-Type", "text/html");

  return new Response("<!DOCTYPE html>" + markup, {
    status: responseStatusCode,
    headers: responseHeaders,
  });
}

Sitemap

To generate sitemap, @balavishnuvj/remix-seo would need context of all your routes.

If you have already created a file to handle all root routes. If not, check above

Add config for your sitemap

import { EntryContext } from "remix";
import { generateSitemap } from "@balavishnuvj/remix-seo";

type Handler = (
  request: Request,
  remixContext: EntryContext
) => Promise<Response | null> | null;

export const otherRootRoutes: Record<string, Handler> = {
  "/sitemap.xml": async (request, remixContext) => {
    return generateSitemap(request, remixContext, {
      siteUrl: "https://balavishnuvj.com",
    });
  },
};

export const otherRootRouteHandlers: Array<Handler> = [
  ...Object.entries(otherRootRoutes).map(([path, handler]) => {
    return (request: Request, remixContext: EntryContext) => {
      if (new URL(request.url).pathname !== path) return null;

      return handler(request, remixContext);
    };
  }),
];

generateSitemap takes three params request, EntryContext, and SEOOptions.

Configuration

  • SEOOptions lets you configure the sitemap
export type SEOOptions = {
  siteUrl: string; // URL where the site is hosted, eg. https://balavishnuvj.com
  headers?: HeadersInit; // Additional headers
  /*
    eg:  
        headers: {
            "Cache-Control": `public, max-age=${60 * 5}`,
        },
  */
};
  • To not generate sitemap for a route
// in your routes/url-that-doesnt-need-sitemap
import { SEOHandle } from "@balavishnuvj/remix-seo";

export let loader: LoaderFunction = ({ request }) => {
  /**/
};

export const handle: SEOHandle = {
  getSitemapEntries: () => null,
};
  • To generate sitemap for dynamic routes
// routes/blog/$blogslug.tsx

export const handle: SEOHandle = {
  getSitemapEntries: async (request) => {
    const blogs = await db.blog.findMany();
    return blogs.map((blog) => {
      return { route: `/blog/${blog.slug}`, priority: 0.7 };
    });
  },
};

Robots

You can add this part of the root routes as did abovecheck above. Or else you can create a new file in your routes folder with robots[.txt].ts

To generate robots.txt

generateRobotsTxt([
  { type: "sitemap", value: "https://balavishnuvj.com/sitemap.xml" },
  { type: "disallow", value: "/admin" },
]);

generateRobotsTxt takes two arguments.

First one is array of policies

export type RobotsPolicy = {
  type: "allow" | "disallow" | "sitemap" | "crawlDelay" | "userAgent";
  value: string;
};

and second parameter RobotsConfig is for additional configuration

export type RobotsConfig = {
  appendOnDefaultPolicies?: boolean; // If default policies should used
  /*
  Default policy
    const defaultPolicies: RobotsPolicy[] = [
    {
        type: "userAgent",
        value: "*",
    },
    {
        type: "allow",
        value: "/",
    },
    ];
  */
  headers?: HeadersInit; // Additional headers
  /*
    eg:  
        headers: {
            "Cache-Control": `public, max-age=${60 * 5}`,
        },
  */
};

If you are using single function to create both sitemap and robots.txt

import { EntryContext } from "remix";
import { generateRobotsTxt, generateSitemap } from "@balavishnuvj/remix-seo";

type Handler = (
  request: Request,
  remixContext: EntryContext
) => Promise<Response | null> | null;

export const otherRootRoutes: Record<string, Handler> = {
  "/sitemap.xml": async (request, remixContext) => {
    return generateSitemap(request, remixContext, {
      siteUrl: "https://balavishnuvj.com",
      headers: {
        "Cache-Control": `public, max-age=${60 * 5}`,
      },
    });
  },
  "/robots.txt": async () => {
    return generateRobotsTxt([
      { type: "sitemap", value: "https://balavishnuvj.com/sitemap.xml" },
      { type: "disallow", value: "/admin" },
    ]);
  },
};

export const otherRootRouteHandlers: Array<Handler> = [
  ...Object.entries(otherRootRoutes).map(([path, handler]) => {
    return (request: Request, remixContext: EntryContext) => {
      if (new URL(request.url).pathname !== path) return null;

      return handler(request, remixContext);
    };
  }),
];
Comments
  • Documentation Missing Information on Setting Priority on Primary Routes

    Documentation Missing Information on Setting Priority on Primary Routes

    Describe the bug

    While there is a mechanism for setting priority on dynamic routes, there is not instruction on how to set priority on primary routes.

    Your Example Website or App

    https://silvermouselive.com

    Steps to Reproduce the Bug or Issue

    This is just a documentation issue

    Expected behavior

    Some instruction on how to set priority for primary routes

    Screenshots or Videos

    No response

    Platform

    Github / Website

    Additional context

    No response

    opened by justinhandley 2
  • feat: make generate functions available on workers and any platform

    feat: make generate functions available on workers and any platform

    This PR introduces a better way to get the Content-Length from the resources, since Buffer is not present on workers I have search an alternative that can work in any platform

    opened by danestves 2
  • add github ISSUE_TEMPLATE files

    add github ISSUE_TEMPLATE files

    What is the change?

    1. add bug_report.md to bug-report.yml to enable Github's form based issue template
      • https://youtu.be/qQE1BUkf2-s?t=23
    2. add config.yml file to help direct users to the helpful pages

    Motivation

    • encourage's bug reporter's to put more care into their bug report before submission
    • this may help maintainer's receive more detailed & higher quality bug report's
    • adds helpful tips for user's during the process of creating a bug/issue report

    Demo of Change

    this PR is similar to this one I created here for another repo recently

    • https://github.com/antvis/G6/blob/master/.github/ISSUE_TEMPLATE/bug_report.yml
    opened by cliffordfajardo 1
  • Replace lodash dependency with lodash.isequal.

    Replace lodash dependency with lodash.isequal.

    Using the lodash dependency brings in the entire lodash library, which is an issue for some serverless environments like Cloudflare Pages/Workers which require a quick start up time. This commit replaces the lodash library with the isEqual function that is being used.

    opened by RossMcMillan92 0
  • No support for pathless layout routes or splat routes

    No support for pathless layout routes or splat routes

    Describe the bug

    When I use a pathless layout route (so I can reuse a layout for several routes), the resulting sitemap.xml routes beneath that layout have an extra slash in them. The splat route is also included by default, which is unexpected.

    As a workaround I had to manually export a handle from every nested route under the pathless route as well as the splat route. The downsides of that are:

    • more code I have to maintain
    • more code needed for future routes beneath the pathless route (which I will probably forget to add 🦄)

    Your Example Website or App

    https://stackblitz.com/edit/remix-seo-pathless-route-bug?file=app/entry.server.tsx

    Steps to Reproduce the Bug or Issue

    1. Go to the stackblitz example
    2. Run npm run dev
    3. Navigate to the sitemap.xml route
    4. Open the viewer in a separate browser window (because the viewer doesn't format XML properly)
    5. See that there are two issues:
    • There is a splat route included (https://blabla.com/*), which is unexpected
    • The nested routes of the pathless layout route have a slash too much in them:
      • https://blabla.com//blog < this has two subsequent slashes, one too many
      • https://blabla.com/ < the trailing slash should not be there

    Expected behavior

    I'd expect the resulting sitemap to only have these routes:

    • https://blabla.com (no trailing slash)
    • https://blabla.com/blog (just a single slash between the base url and 'blog')
    • The splat route is not included, unless it has an explicit handle returning the possible dynamic routes.

    Screenshots or Videos

    image

    Platform

    • OS: macOS
    • Browser: Chrome
    • Version: 107.0.5304.110

    Additional context

    No response

    opened by JoepKockelkorn 1
Owner
Balavishnu V J
Balavishnu V J
Build Schema.org graphs for JavaScript Runtimes (Browser, Node, etc). Improve your sites SEO with quick and easy Rich Results.

schema-org-graph-js The quickest and easiest way to build Schema.org graphs for JavaScript Runtimes (Browser, Node, etc). Status: ?? In Development Pl

Harlan Wilton 17 Dec 21, 2022
Remix Stack for deploying to Vercel with remix-auth, Planetscale, Radix UI, TailwindCSS, formatting, linting etc. Written in Typescript.

Remix Synthwave Stack Learn more about Remix Stacks. npx create-remix --template ilangorajagopal/synthwave-stack What's in the stack Vercel deploymen

Ilango 56 Dec 25, 2022
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
we learn the whole concept of JS including Basics like Object, Functions, Array etc. And Advance JS - Understanding DOMs, JQuery, Ajax, Prototypes etc.

JavaScript-for-Complete-Web Development. we learn the whole concept of JS including Basics like Object, Functions, Array etc. And Advance JS - Underst

prasam jain 2 Jul 22, 2022
Simple CLI to warm the cache of social images in all pages from a sitemap.

warm-social-images Simple CLI to warm the cache of social images in all pages from a sitemap. Why Using serverless functions to generate images for so

Travis Fischer 9 Jul 7, 2022
The fastest way ⚡️ to create sitemap in your Deno Fresh project 🍋

Fresh SEO ??     Quickly creating sitemaps for your Deno Fresh project. Getting Started Run the setup at the root of your project. deno run

Steven Yung 34 Dec 19, 2022
Script to fetch all NFT owners using moralis API. This script output is a data.txt file containing all owner addresses of a given NFT and their balances.

?? Moralis NFT Snapshot Moralis NFT API will only return 500 itens at a time when its is called. For that reason, a simple logic is needed to fetch al

Phill Menezes 6 Jun 23, 2022
An online .txt file compressor, de-compressor tool which uses Huffman Coding for Lossless data compression.

Text File Compressor De-compressor Web App This webapp uses Huffman Coding for Text Compression and De-compression. Made with JavaScript, HTML5 and CS

Samir Paul 10 Dec 25, 2022
A lightweight (~850 B) library for easy mac/window shortcut notation. kbd-txt convert shortcut text depending on the type of OS (window/linux/mac).

kbd-txt A lightweight (~850 B) library for easy mac/window shortcut notation. kbd-txt convert shortcut text depending on the type of OS (window/linux/

Minung Han 6 Jan 1, 2023
Zod utilities for Remix loaders and actions.

Zodix Zodix is a collection of Zod utilities for Remix loaders and actions. It abstracts the complexity of parsing and validating FormData and URLSear

Riley Tomasek 172 Dec 22, 2022
The Remix Stack for deploying to AWS with DynamoDB, authentication, testing, linting, formatting, etc.

The Remix Stack for deploying to AWS with DynamoDB, authentication, testing, linting, formatting, etc.

Remix 311 Jan 1, 2023
The Remix Stack for deploying to Fly with Supabase, authentication, testing, linting, formatting, etc.

Remix Supa Fly Stack Learn more about Remix Stacks. npx create-remix --template rphlmr/supa-fly-stack What's in the stack Fly app deployment with Doc

Raphaël Moreau 157 Jan 7, 2023
The Remix Blog Stack for deploying to Fly with MDX, SQLite, testing, linting, formatting, etc.

Remix Speed Metal Stack Learn more about Remix Stacks. npx create-remix --template Girish21/speed-metal-stack Remix Blog ?? This blog starter template

Girish 141 Jan 2, 2023
The Remix Stack for deploying to Fly with SQLite, authentication, testing, linting, formatting, etc.

Remix Indie Stack Learn more about Remix Stacks. npx create-remix --template remix-run/indie-stack What's in the stack Fly app deployment with Docker

Remix 688 Dec 30, 2022
The Remix Stack for deploying to Fly with PostgreSQL, authentication, testing, linting, formatting, etc.

The Remix Stack for deploying to Fly with PostgreSQL, authentication, testing, linting, formatting, etc.

Remix 677 Jan 2, 2023
The Remix Stack for Web2 apps and Web3 DApps with authentication with Magic, testing, linting, formatting, etc.

Remix French House Stack Learn more about Remix Stacks. npx create-remix --template janhesters/french-house-stack What's in the Stack? The French Hou

Jan Hesters 26 Dec 26, 2022
A crawler that crawls the site's internal links, fetching information of interest to any SEO specialist to perform appropriate analysis on the site.

Overview ?? It is a module that crawls sites and extracts basic information on any web page of interest to site owners in general, and SEO specialists

Yazan Zoghbi 2 Apr 22, 2022