Frontity release: Server Extensibility

Starting from @frontity/core 1.6.0, you can now include server middleware in your packages! :tada:

It’s super simple: just add your middleware functions inside your package definition under the new server property.

export default {
  server: {
    myPackage: {
      myServerLogic: async ({ ctx, state, libraries, actions }) => {
        const someCustomHeader = await fetchSomething();
        ctx.set("X-Frontity-Some-Data", someCustomHeader);
        next();
      },
    },
  },
};

These functions receive the usual state/actions/libraries, but also the Koa’s ctx and next. Please refer to the Koa documentation to learn how to use ctx and next.

You can also import other Koa utilities and use them like in any other Koa app:

import { get } from "koa-route";

export default {
  server: {
    myPackage: {
      myRobotsTxt: get("/robots-one.txt", ({ ctx }) => {
        ctx.type = "text/plain";
        ctx.body = "User-agent: *\nAllow: /\n";
      }),
    },
  },
};

If you need to import server-only code, you need to divide the entry point of your package in client.js and server.js (instead of index.js).

You can type your server functions using the new Server type exported from frontity/types:

import { Server, Package } from "frontity/types";

interface MyPackage extends Package {
  // other state, actions...
  server: {
    myPackage: {
      myServerLogic: Server<MyPackage>;
    };
  };
}

export default MyPackage;

If you need to type your server functions in a call of a Koa middleware, you can manually cast the Server<MyPackage> type:

import { Server } from "frontity/types";
import { get } from "koa-route";
import MyPackage from "../types";

export default {
  server: {
    myPackage: {
      myRobotsTxt: get("/robots-one.txt", (({ ctx }) => {
        ctx.type = "text/plain";
        ctx.body = "User-agent: *\nAllow: /\n";
      }) as Server<MyPackage>),
    },
  },
};

If you want more details, you can read the Final Implementation Proposal and the Pull Request.

Huge kudos to @orballo for implementing this long-awaited feature!! :tada::tada:


@frontity/core@1.16.0

Minor Changes

@frontity/types@1.10.0

Minor Changes

3 Likes

Hey @luisherranz and @orballo ,

I know you guys are not supporting the project anymore but it would be great if we can get a little bit more input about this as its a great feature and unfortunately the information above is not enough for me to get it going. Are there maybe some demo projects with this? The reason I currently need it is because we want to implement custom 5xx error pages for Vercel. (How to handle internal server 500 errors - display custom internal server error page - #8 by leogardner12)

In this example that you gave, where does next() come from?

export default {
  server: {
    myPackage: {
      myServerLogic: async ({ ctx, state, libraries, actions }) => {
        const someCustomHeader = await fetchSomething();
        ctx.set("X-Frontity-Some-Data", someCustomHeader);
        next();
      },
    },
  },
};

I tried this:

server: {
    theme: {
      myServerLogic: async ({ ctx, next }) => {
        ctx.set("X-Frontity-Some-Data", "some header");
        next();
      },
    },
  },

But then I just get a not found page. I also tried logging and it works, but it seems like next() is not working

That’s weird, it’s exactly like one of the e2e tests and the test works fine: frontity/server.ts at dev · frontity/frontity · GitHub

Do you have any other middleware running?

EDIT: Maybe you need to return next()?

@luisherranz it is because next()needs to be invoked with await. Then it works properly.

Should be something like this

  export default {
  server: {
    myPackage: {
      myServerLogic: async ({ ctx, state, libraries, actions }) => {
        const someCustomHeader = await fetchSomething();
        ctx.set("X-Frontity-Some-Data", someCustomHeader);
        await next();
      },
    },
  },
};

Nice :slightly_smiling_face: