Tag: NextJS
Security Headers in Next.JS

Security Headers in Next.JS

To ensure your site is protecting its users. several security headers can be set to help prevent certain attacks against websites.

For Next.JS these are set in the next.config.js file. Typically I use the following config.

1/** @type {import('next').NextConfig} */
2const nextConfig = {
3 async headers() {
4 return [
5 {
6 source: '/:path*',
7 headers: [
8 {
9 key: 'X-Frame-Options',
10 value: 'SAMEORIGIN',
11 },
12 {
13 key: 'Content-Security-Policy',
14 value: "frame-ancestors 'none'",
15 },
16 {
17 key: 'Referrer-Policy',
18 value: 'same-origin',
19 },
20 {
21 key: 'Strict-Transport-Security',
22 value: 'max-age=15768000',
23 },
24 {
25 key: 'X-Content-Type-Options',
26 value: 'nosniff',
27 },
28 {
29 key: 'X-XSS-Protection',
30 value: '1; mode=block',
31 },
32 ],
33 },
34 ];
35 },
36};
37
38module.exports = nextConfig;
39

Content-Security-Policy

Setting frame-ancestors: none is similar to X-Frame-Options and will prevent a site loading in an ancestor frame, iframe. object or embed.

X-Frame-Options

Indicates whether a browser should be allowed to render a page in a frame, iframe, embed or object tag. By setting to SAMEORIGIN this ensures the site is only rendered in this way on itself and not on other sites.

Click-jacking attacks commonly use display other sites within themselves to fool a user as to what site they are on.

Referrer-Policy

Referrer Policy sets how much information is sent with the referrer header. By setting to same-orign, the referrer header is only sent on same origin requests.

Strict-Transport-Security

This header instructs the browser to only access the site over https. By setting the age it instructs the browser to remember this setting for the number of seconds given.

X-Content-Type-Options

Setting to nosniff will block a request if the destination is of type style and the MIME type is not text/css, or of type script and the MIME type is not a JavaScript MIME type.

X-XSS-Protection

This is a non-standard header and isn't supported in any current browsers but has history support with Chrome and Edge.

The header instructs the browser how to handle any cross-site scripting attacks it detects on the page. Setting to 1; mode-block will prevent the page from rendering if an attack is detected.

Storybook + NextJs with TypeScript Paths set

Storybook + NextJs with TypeScript Paths set

Previously I have written about making your code neater by using TypeScript paths. Since then I have found that the NextJs create app Wizzard now asks you about setting this, so that from the get-go you will now likely start with it set in an application and have neater code for the better.

However, if you then try using Storybook then you're likely to see an error Module not found: Error: Can't resolve '@/components...' on any component that has an import using one of the paths.

Storybook error relating to paths

Quite simply, Storybook does not pick up the setting in your tsconfig.json file and it has no idea where to import the module from.

To get around this issue we need to also tell Storybook about the paths we've set in .storybook\main.ts. We do this by adding a resolve alias to the config returned by webpackFinal. The code example here sets an alias that will match the default path set by NextJs in its create app wizzard.

1import type { StorybookConfig } from "@storybook/nextjs";
2import path from "path";
3
4const config: StorybookConfig = {
5 // Other config removed for clarity
6 webpackFinal: async (config, { configType }) => {
7 if (!config.resolve) {
8 return config;
9 }
10
11 config.resolve.alias = {
12 ...config.resolve.alias,
13 "@": path.resolve(__dirname, "../src"),
14 };
15
16 return config;
17 },
18};
19export default config;
20

Of course if you have other paths set then you can add them here as well. When you next run storybook the error should be gone and your components now show in Storybooks UI.

Making your code neater with TypeScript Paths

Making your code neater with TypeScript Paths

When I started doing development with TypeScript one of the first things that irked me was all those relative paths in import statements.

Code organisation and creating layers for maintainability is one of the things I'm most OCD about, so things like a service to call Prismics API end up in a folder called services. However, to then use it in a NextJs page a few folders deep ends up with an import statement such as this.

1import { prismicService } from '../../../src/services/Prismic/Prismic.service'

Yuck. Look at all those ../../ and to make things worse this also means if you move a file its no longer pointing in the right place.

Fortunately, there is a solution. Typescript supports path mapping through the tsconfig.json file.

First set a base URL property in the compiler options.

1{
2 "compilerOptions": {
3 "baseUrl": ".",
4 }
5}

At this point, you could get rid of all those ../ and just start at the src. e.g.

1import { prismicService } from 'src/services/Prismic/Prismic.service'

This is a lot better and we can now move files around, but it's still not perfect. The fact it's in a folder called src is a bit irrelevant for my liking.

Using the path's property we can define a grouping even lower down. e.g.

1// tsconfig.json
2{
3 "compilerOptions": {
4 "baseUrl": ".",
5 "paths": {
6 "@Services/*": ["src/services/*"]
7 }
8 }
9}
10
11// New import
12import { prismicService } from '@Services/Prismic/Prismic.service'

For more information on TypeScript paths check out the official documentation here.

Going a bit further

As well as just making your code a bit easier to read. This also makes a simple solution to swapping out code in a way that would otherwise need a dependency injection framework.

Coming from a .net background where dependency injection and interfaces are frequently used there can be a desire to do it with TypeScript too. Although it is possible (you can read my blog post on dependency injection with NextJs and Tsyringe here) it has quite a heavy overhead to the amount of code you need to write.

A simpler solution can be like what Vercel have done in NextJs commerce for the different providers.

Check out this part of their tsconfig file.

1 "paths": {
2 "@lib/*": ["lib/*"],
3 "@utils/*": ["utils/*"],
4 "@config/*": ["config/*"],
5 "@assets/*": ["assets/*"],
6 "@components/*": ["components/*"],
7 "@commerce": ["../packages/commerce/src"],
8 "@commerce/*": ["../packages/commerce/src/*"],
9 "@framework": ["../packages/local/src"],
10 "@framework/*": ["../packages/local/src/*"]
11 }

That @framework section at the end defaults to local, but gets swapped out to a commerce provider like bigcommerce or shopify. As each one implements the same methods, anything importing from @framework simply uses the correct code when the solution is built.

Dependency Injection with NextJS and TypeScript

Dependency Injection with NextJS and TypeScript

Coming from a backend world, one thing that stands out when you start writing code in JavaScript, be it React, NextJS, Vue etc, is there's no great focus on code structure. Things like TypeScript bring back the concept of type checking that you'd be used to with a compiled language, and NextJS will give anyone familiar with ASP.NET MVC an alternative pattern for how a website should be constructed. But you can get the whole way through the NextJS tutorial without concepts like single responsibility, inversion of control or CQRS being mentioned once.

Now if your building a small site, maybe you can get away with not knowing or imlementing these things, but if you want to make code that's scalable or maintainable, it doesn't matter if you write in JavaScript or C# the same issues will exist but fortunately the same solutions do too.

Code Smells

Lets take a look at one of the functions on this site which gets the latest blog posts on the homepage.

1import gql from 'graphql-tag';
2import { Client, ApolClient } from '../prismicHelpers'
3
4// Models
5import { FeaturedPost } from "../../Models/FeaturedPost"
6
7const latestPostsQuery = gql`
8query latestPosts($category: String) {
9 allPosts (where : {category: $category}, first : 10, sortBy: post_date_DESC){
10 edges {
11 node {
12 category {
13 ... on Categories {
14 name
15 }
16 },
17 title,
18 image,
19 post_date
20 _meta {
21 uid
22 }
23 }
24 }
25 }
26}
27`;
28
29export const getLatestPosts = async (category?: String) : Promise<FeaturedPost[]> => {
30 const queryOptions = {
31 query: latestPostsQuery,
32 variables: { category },
33 };
34
35 return new Promise((resolve, reject) => { ApolClient.query(queryOptions).then(response => {
36 var posts: Array<FeaturedPost> = [];
37 response.data.allPosts.edges.map((edge: { node: { title: { text: any; }[]; category: any; image: any; post_date: Date; _meta: { uid: any; }; }; }, key: any) => {
38 posts.push({
39 type: "post",
40 title: edge.node.title[0].text,
41 image: edge.node.image,
42 uid: edge.node._meta.uid,
43 category: edge.node.category?.name,
44 postDate: edge.node.post_date
45 })
46 })
47 resolve( posts);
48 }).catch(error => {
49 reject(error);
50 });
51});
52};

This bit of code queries Prismic's GraphQL API to get the latest 10 articles for a category and then maps the result onto an internal FeaturedPost model.

Some good things about it:

  • The Prismic logic is abstracted away from the rest of the application by mapping the results to a model.
  • It follows single responsibility by doing just 1 job.
  • All config for the API (e.g. URI) are separated into a common function rather than being duplicated.

However even though I've seen countless JavaScript examples which don't even do these good things, if this were C# and I was reviewing a pull request, I'd say it smelt and needed changing.

Here's some bad things about it:

  • It's taking a dependency on Apollo Client, which while is a great client for doing GraphQL queries, at the rate JS frameworks come and go we can't say we'll never replace it and in a large application that has countless queries that would be a lot of code to update.
  • There's also no way anything can call this without also taking a dependency on it. That means I now have a hierarchy of at least 3 functions with each one dependent on the next. If I ever wanted to add unit tests to my project I'd have a big problem.

Dependency Injection using TSyringe

These issues can be solved using Dependency Injection. There's quite a few around but the one I've chosen is TSyringe (https://github.com/microsoft/tsyringe).

It's built by Microsoft, which gives me some confidence in the amount of QA that will have gone into it, but more importantly it works the way I expect a DI framework to work. There's a good chance this could be because I'm used to working in the Microsoft stack and it therefore has natural similarities to DI frameworks in their own languages.

How to add TSyringe to a NextJS project

There's nothing specifically NextJS about TSyringe, you could use it with any TypeScript/JavaScript project, it just happens that that's what my blog is built using. However it also had a few issues getting it to work and I couldn't any articles around which explained how.

To set it up...

First install the package tsyring and reflect-metadata using npm.

1npm install --save tsyringe reflect-metadata

Modify your tsconfig.json file to include the following settings, which will allow the use of decorators in TypeScript.

1{
2 "compilerOptions": {
3 "experimentalDecorators": true,
4 "emitDecoratorMetadata": true
5 }
6}

Now there's a few more packages your going to need. As of NextJS 12, Babel is no longer used and has been replaced with SWC to provide faster compile times. However TSyringe and most other DI frameworks use decorators to function (hence the tsconfig.js setting to turn them on), but the setting for SWC to allow this set to false in NextJS and there's no way for you to provide your own config. Fortunately Babel is still supported and you can customise it. Hopefully a future version of NextJS will address this.

Install the following packages as dependencies.

1npm install -D @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators babel-plugin-transform-typescript-metadata

Add a .bablerc file to your project with the following settings.

1{
2 "presets": ["next/babel"],
3 "plugins": [
4 "babel-plugin-transform-typescript-metadata",
5 ["@babel/plugin-proposal-decorators", { "legacy": true }],
6 ["@babel/plugin-proposal-class-properties", { "loose": true }]
7 ]
8}

Finally add this import to your _app.tsx file.

1import "reflect-metadata";

Using TSyringe

We're now ready to convert the code I had before into something more maintainable.

First I'm going to create a GraphQL Client interface which all my queries will use when they want to call a graph API. This has one function called query, which my functions will pass the graph syntax too along with a variables object.

1import { DocumentNode } from "graphql";
2
3export interface graphClient {
4 query(query: DocumentNode, variables?: {}): any;
5}

With this interface I can now turn my getLatestPosts function into a class with a constructor that will take in the instance of graphClient.

1import { inject, injectable } from "tsyringe";
2import { iGetLatestPosts } from "./iGetLatestPosts";
3import gql from "graphql-tag";
4import { graphClient } from "../iGraphQl";
5
6// Models
7import { FeaturedPost } from "../../Models/FeaturedPost";
8
9@injectable()
10export class getLatestPosts implements iGetLatestPosts {
11 graphClient: graphClient;
12
13 constructor(@inject("graphClient") private graphClientParam: graphClient) {
14 this.graphClient = graphClientParam;
15 }
16
17 private latestPostsQuery = gql`
18 query latestPosts($category: String) {
19 allPosts(
20 where: { category: $category }
21 first: 10
22 sortBy: post_date_DESC
23 ) {
24 edges {
25 node {
26 category {
27 ... on Categories {
28 name
29 }
30 }
31 title
32 image
33 post_date
34 _meta {
35 uid
36 }
37 }
38 }
39 }
40 }
41 `;
42
43 public getLatestPosts = async (
44 category?: String
45 ): Promise<FeaturedPost[]> => {
46 return new Promise((resolve, reject) => {
47 this.graphClient
48 .query(this.latestPostsQuery, { category })
49 .then((response: any) => {
50 var posts: Array<FeaturedPost> = [];
51 response.data.allPosts.edges.map(
52 (
53 edge: {
54 node: {
55 title: { text: any }[];
56 category: any;
57 image: any;
58 post_date: Date;
59 _meta: { uid: any };
60 };
61 },
62 key: any
63 ) => {
64 posts.push({
65 type: "post",
66 title: edge.node.title[0].text,
67 image: edge.node.image,
68 uid: edge.node._meta.uid,
69 category: edge.node.category?.name,
70 postDate: edge.node.post_date,
71 });
72 }
73 );
74 resolve(posts);
75 })
76 .catch((error: any) => {
77 reject(error);
78 });
79 });
80 };
81}

Some things to note in this new class.

  • It's also now implementing an interface so that it can instantiated using DI.
  • The @injectable decorator allows TSyringe to inject the dependencies at runtime.
  • The constructor is decorating a parameter with @inject("graphClient") which means that parameter will be injected at runtime with whatever is configured against the graphClient token.
  • There are imports from tsyringe.
  • There are no references to the implementation of graphClient.
  • My function now has zero dependencies on Apollo Client and doesn't even know it's being used.

My implementation of graphClient looks like this.

1import { autoInjectable } from "tsyringe";
2import { DocumentNode } from "apollo-link";
3import ApolloClient from "apollo-client";
4import { NormalizedCacheObject } from "apollo-cache-inmemory";
5import { graphClient } from "./iGraphQl";
6
7@autoInjectable()
8export class apolloGraphClient implements graphClient {
9 apolloClient: ApolloClient<NormalizedCacheObject>;
10
11 constructor(apolloClient: ApolloClient<NormalizedCacheObject>) {
12 this.apolloClient = apolloClient;
13 }
14
15 public query = async (query: DocumentNode, variables?: {}): Promise<any> => {
16 const queryOptions = {
17 query: query,
18 variables: variables,
19 };
20
21 return new Promise((resolve, reject) => {
22 this.apolloClient
23 .query(queryOptions)
24 .then((response: any) => {
25 resolve(response);
26 })
27 .catch((error: any) => {
28 reject(error);
29 });
30 });
31 };
32}

Essentially all this function does is pass the parameters to the query function to an Apollo Client's query function. The Apollo Client itself is also being injected!

You may have expected this file to also instantiate the Apollo Client, and it could have, but I've gone to the extreme and the single purpose of this file is to act as a bridge between the business logic queries and what client is being used, so for that reason its injected.

You'll also notice that this time I'm decorating the class with @autoInjectable() and there is no decorator on the constructor parameter. More on this in a bit.

The homepage page for this site now looks like this.

1import Head from "next/head";
2
3import React, { useState } from "react";
4import { container } from "tsyringe";
5import { GetStaticProps } from "next";
6import Layout from "../layouts/layout";
7import { FeaturedRow1, FeaturedRow1Model } from "../components/featured-row-1";
8import SectionHeading from "../components/section-heading";
9import { iGetLatestPosts } from "../utils/queries/iGetLatestPosts";
10
11export default function Home({
12 latestPosts,
13 webDevelopmentPosts,
14 sitecorePosts,
15 devOpsPosts,
16}: {
17 latestPosts: FeaturedRow1Model;
18 webDevelopmentPosts: FeaturedRow1Model;
19 sitecorePosts: FeaturedRow1Model;
20 devOpsPosts: FeaturedRow1Model;
21}) {
22 return (
23 <Layout>
24 <Head>
25 <title>Hi My Name Is Tim</title>
26 </Head>
27 <SectionHeading heading="Latest Posts" link="blog"></SectionHeading>
28 <FeaturedRow1 posts={latestPosts}></FeaturedRow1>
29 <SectionHeading heading="Web Development" link="web-development"></SectionHeading>
30 <FeaturedRow1 posts={webDevelopmentPosts}></FeaturedRow1>
31 <SectionHeading heading="Sitecore" link="sitecore"></SectionHeading>
32 <FeaturedRow1 posts={sitecorePosts}></FeaturedRow1>
33 <SectionHeading heading="Devops" link="devops"></SectionHeading>
34 <FeaturedRow1 posts={devOpsPosts}></FeaturedRow1>
35 </Layout>
36 );
37}
38
39export const getStaticProps: GetStaticProps = async () => {
40 // Resolve interface for iGetLatestPosts
41 const instance = container.resolve<iGetLatestPosts>("iGetLatestPosts");
42
43 const latestPosts = await instance.getLatestPosts();
44 const webDevelopmentPosts = await instance.getLatestPosts("X8kFhxIAACcAn9oY");
45 const devOpsPosts = await instance.getLatestPosts("X8kFlRIAACkAn9pa");
46 const sitecorePosts = await instance.getLatestPosts("X8kFeBIAACkAn9nV");
47
48 return {
49 props: {
50 latestPosts: latestPosts,
51 devOpsPosts: devOpsPosts,
52 sitecorePosts: sitecorePosts,
53 webDevelopmentPosts: webDevelopmentPosts,
54 },
55 };
56};

Pages in NextJS TypeScript don't use classes, so we can't do constructor injection to get the instance of our getLatestPosts query class. Instead we are using container.resolve<iGetLatestPosts>("iGetLatestPosts") to get the instance to token name iGetLatestPosts from the DI container.

Lastly in the _app.tsx file I am registering the classes on the container. I'm only including the relevant bit of the file here.

1container.registerInstance(
2 ApolloClient,
3 new ApolloClient({
4 link: PrismicLink({
5 uri: prismicGraphUri,
6 repositoryName: prismicRepoName,
7 }),
8 cache: new InMemoryCache({ fragmentMatcher }),
9 })
10);
11
12container.register("graphClient", apolloGraphClient);
13container.register("iGetLatestPosts", getLatestPosts);

For the Apollo Client I am using register instance to register a specific instance and creating it at the same time. Notice the first parameter is the class name.

For the graph client and getLatestPosts query I am using the register method and rather than creating the instance of my implementation, just passing the class as the second parameter. The framework will handle creating an instance of them for me.

Notice the first parameter for the second two are strings rather than the actual interfaces. These are token names that the container will use to reference the instance value. With the Apollo Client, the framework will figure out the token name when it adds it to the container, but it can't do the same for an interface (if you try you will get an interface cannot be used as a type error) so you have to provide the token name instead. This is also the reason why the graph client implementation didn't need to use a string to inject the class instance in the constructor, whereas the other places did.

Personally I feel this is a weakness in the framework as it also means there is no type checking either when registering or resolving from the container.

Summary

We have seen that using JavaScript doesn't prevent us from using the same concepts as we would with any other more traditional backend language.

We've also seen how a NextJS application while slightly more awkward, can still be set up to use dependency injections.

Finally we've had a look at how to actually configure some code to have complete separation between logic within a solution.

Data Fetching options with NextJS

Data Fetching options with NextJS

When it comes to building websites in node, I'm a big fan of using NextJS. There's a lot of static site generators around but one of the things I really like about NextJS is not only the fact you have different options for data fetching, but that they're also really easy to use.

Static Generation

If you've somehow missed it, static site generation is the idea that to make your site run as fast as possible for every user, you pre-generate all the pages so that when a visitor makes a request for the page, all the server needs to do is return a file. It's a bit like the start of the internet when everything was an html file, before we became clever and started generating files dynamically on the fly. With all the processing removed, the time to first byte is greatly reduced.

To do this with NextJs you add a function called getStaticProps to your page and when the site is built, this will get all the static content and make it available to the page to be pre-generated.

1export async function getStaticProps(context) {
2 return {
3 props: {}, // will be passed to the page component as props
4 }
5}

Or if your using TypeScript.

1import { GetStaticProps } from 'next'
2
3export const getStaticProps: GetStaticProps = async (context) => {
4 // ...
5}

Static Generation for Dynamic Paths

So that last example would generate content for a page, but what if you have a headless CMS and unlimited pages. You certainly couldn't create a file for each one with it's own getStaticProps function.

For this there is getStaticPaths. Like getStaticProps this function will run at the build time and is used to find and return all the dynamic paths for the page. Think of your page as the page template and this function is getting all the pages that relate to it. This is how I generate all the blog post pages on this site.

The function returns an object with two values. First paths which is a list of all the routes and the parameters for them. e.g. a list of the id's to get the data for a page with. The second is fallback: false, this tells NextJs that if the request is for a route not in the paths list, then returns a 404.

1export async function getStaticPaths() {
2 return {
3 paths: [
4 { params: { ... } }
5 ],
6 fallback: false
7 };
8}

The params are then passed to the getStaticProps function so that it can pre-generate all the page content.

1// This also gets called at build time
2export async function getStaticProps({ params }) {
3 // params contains the post `id`.
4 // If the route is like /posts/1, then params.id is 1
5 const res = await fetch(`https://.../posts/${params.id}`)
6 const post = await res.json()
7
8 // Pass post data to the page via props
9 return { props: { post } }
10}

If you'd prefer Typescript then this is the alternative.

1import { GetStaticPaths } from 'next'
2
3export const getStaticPaths: GetStaticPaths = async () => {
4 // ...
5}

The Good Old Dynamic Way

Static's great, but sometimes are pages just aren't static enough for it to make sense. Here's where getServerSideProps comes in. With getServerSideProps your page will get generated on the server at runtime.

At this point you head may explode because at some point someone told you static site generators were faster, more secure etc because nothing runs on the server. Well erm.................. there is a server and it can generate files, so they were er... wrong.

1export async function getServerSideProps(context) {
2 return {
3 props: {}, // will be passed to the page component as props
4 }
5}

Or with TypeScript.

1import { GetServerSideProps } from 'next'
2
3export const getServerSideProps: GetServerSideProps = async (context) => {
4 // ...
5}

Incremental Static Regeneration

Lets take are mind back to that first static page generation. It's great for the end user, but the downside is that each time you need to change something on a page you have to regenerate the entire site. On a big site, that's an issue.

Incremental Static Regeneration lets you tell NextJs that while you want the page the be pre-built, you also want that version to expire and get generated again. Think of this like the expiry time on a CDN, but configured at a page level in your site.

To use it the getStaticProps function needs to return a revalidate value. This gives the instruction of up to how frequently the page should be generated.

1export async function getStaticProps() {
2 const res = await fetch('https://.../posts')
3 const posts = await res.json()
4
5 return {
6 props: {
7 posts,
8 },
9 // Next.js will attempt to re-generate the page:
10 // - When a request comes in
11 // - At most once every 10 seconds
12 revalidate: 10, // In seconds
13 }
14}

Part Static Generation

So lets say we don't need our pages to expire other than when we rebuild the site, and we also don't want to generate every page in our site because there 2000 of them and it will take ages, but there's also some really important pages that we don't want any user to have to wait for.

Well if we go back to our static generation for dynamic routes example and take another look at that fallback value we can do something different.

Fallback can be set to 3 values, false, true and blocking. False will return a 404 if the route didn't exist, but true and blocking gives us an option to decide at runtime if we want to generate a page or issue a 404.

Setting fallback to true will return a fallback page, getStaticProps will then generate the page and this will then get swapped with the fallback version. Blocking is similar but will wait for the new page to be generated rather than initially issuing a fallback.

By doing this, the paths we return in the props will get generated at build time (we can restrict this to just our important pages), and all the others will function like a regular CDN.

Summary

So as you can see, there's a whole host of options to fetching data, when you do it, how you do it, how long it lives for and I didn't even cover the fact you could still fetch data dynamically when the page has loaded on the client! If we did this then we can pre-generate all the static parts of our page and then populate other bits dynamically after on the client.

JavaScript frameworks explained to an ASP.NET dev

JavaScript frameworks explained to an ASP.NET dev

For most of my career I've been an ASP.NET dev and a JavaScript dev. If I was going to say I was more of an expert in one of them it would be the .NET side of things but I've never really lost touch with JavaScript.

Right now I think it's fair to say technologies in the world are starting to shift how we build websites, with JavaScript frameworks reaching a point with features like static site generation where they actually now offer a decent performance incentive to use them. At some point Blazor may get to a point where it reverses this, but right now there's a compelling argument to move.

For a ASP.NET dev this can be a daunting task. You might be thinking of trying out a headless CMS with a JavaScript front end, but just take a look at this screen grab from Prismic's sdk list.

There's 7 different JavaScript based SDK's listed there! Over half of the total and none of them are that Angular thing you had heard about. Where do you start?

Lets compare to .NET

Well recently I've been updating my JS skills again trying out some of the frameworks I hadn't used before, so I thought I'd share some learnings. The good news is as always it's not really as different as it first seems. To take some of the pain out of understanding what all these frameworks are I thought it would be good to try and relate them back to .NET and what the almost equivalent is.

Assembly Code

No not actual assembler but what does our code actually compile to. In the .NET world we have CIL (Common Intermediate Language), previously known as MSIL (Microsoft Intermediate Language) that our C#, F#, VB etc all compile down to before then being converted to the correct machine code for where they run.

In the front end world think of JavaScript being a bit like this (apart from the fact you actually write JavaScript and we don't write CIL).

View Engine

To render views into HTML, in the ASP.NET world we have Razor, but not just Razor. We also have WebForm, Brail, Bellevue, NDjango (see more here), it just happens that we mostly just use Razor.

I see the equivalents of these being ReactJS, VueJS and Angular. Its not an exact match as they also aren't exact equivalents or each other, but they're largely your functionality that will take a model and turn it into HTML.

Web Application Framework

The problem with the name framework is it applies to basically anything, but this is what I'm going with for describing ASP.NET MVC/ASP.NET Razor Pages/Web Forms, you know all those things built on-top of .NET that make it a website rather than a desktop app. They do things like routing, organising our files into controller and view folders, know how to respond to http requests etc.

Here we have Next.js, Nuxt.js and maybe Gatsby. The link between these and View Engine is a bit stronger than the ASP.NET MVC world as you essentially have a one to one mapping Next.js -> React, Nuxt.js -> Vue but they are what adds routing, static site generation and organization to your code.

Lower Level Framework

Now this one could be wrong :)

In .NET we have different version of the framework. e.g. .NET Framework /3.5/4, .NET Core, .NET 5, Mono. On the front end side they have Node.

Languages

In .NET we have choices including C#, F#, VB among other.

JavaScript has JavaScript (which I know I said was assembly), TypeScript, Coffee Script maybe more.

Not so daunting

There's probably a bunch of flaws with my comparison list and reasons people can point out why things I've said are the same are in fact different, but my point was really to show that while .NET may appear as one button on a SDK list alongside 7 JavaScript based SDK's its not that different. Out of the 7 Node is based on JavaScript. Vue and React are based on Node, and Next/Gatsby/Nuxt are based on Vue/React. There just isn't the same concept of all of it being built by one company or one particular combination being dominant in the same way that ASP.NET MVC + C# + Razor has been for the last generation of .NET websites.