Getting started with Next.js

I’ve been wanting to try Next.js for a while having seen many comments about it and podcasts talking about it. But what is Next.js?

Obviously your best source of information is to go and read the Next.js. website, but the basics are that it’s a React based framework for developing web applications or even full-stack web applications using React and features from React as the basis.

Then the next question you might have is why use Next.js over React?

Next.js builds upon React and give you a lot of tooling/libraries out of the box, such as routing, API calls etc. From this perspective you can get up and running very quickly. Next.js is a React framework, so we still write React code, but with Next.js lots of things just run out of the box. So why use Next.js over React, I guess the answer really is down to the developer experience, the speed and simplicity of getting things up and running. Life any framework that sits atop a library (or framework) such as React, we also need to be mindful that updates to React may not immediately appear in Next.js. Next.js supports SSR whereas, whilst (currently) React does not by default, but I know there’s lots going on with SSR on React.

As I’ve not used Next.js for anything other than learning I cannot say whether one should use Next.js over React or vice versa, but certainly getting up and running with Next.js is simple and as mentioned, some stuff such as routing is built in, so we don’t need to worry about that, but then once your React app has all those libraries what’s the gain in being with Next.js? I don’t have enough experience with Next.js to answer that, so let’s just get started and play…

Getting up and running

This is just a very short getting started (I’m using the current latest which is 14.0.4)

yarn create next-app

Note: Add @latest if you prefer to the command or the version you want to work with

You’ll be prompted for the name of your app as well as other configuration parameters. All I did was give the application a name and stick with all the default parameters, I am using TypeScript so ensure that one’s selected if you’re following through with the code here.

The app folder contains the page.tsx file which is essentially our main page.

Once create we can run the app using the following to run the development server

yarn dev

Bu default this will use port 3000, if you want to run on a different port change the dev script in package.json like this or when you run dev it will automatically pick the next port.

"dev": "next dev -p 3001",

Further configuration for Next.js takes place in the next.config.js file in the route folder of your app (i.e. where package.json is) and it’s a .js as it’s not passed to the Typescript (or Babel) transpiler.

With Next.js the app folder (as you’d probably expect) is where we find the entry point to our application, in the page.tsx file.

As mentioned, Next.js is built upon React, so we can start coding React components just like a CRA created application.

Suspense in React 18

Suspense is a JSX Element/Component which we use to wrap around child components such that whilst they are loading, we can display something else, like a progress page or other fallback page.

This has obvious uses when your React application starts up and potentially loads data from a remote source etc.

Suspense understands, what the React documentation calls “Suspense-enabled data sources”. These include data fetching frameworks such as Relay and Next.js. Also the lazy import keyword lazy and also can understand promises with the (currently experimental) use hook.

Before we use components within the Suspense boundary, they need to be lazy imported.

A simple example of usage would be

<Suspsense fallback={<Loading />}>
   <Header />
   <NavBar />
   <Content />
   <StatusBar />
</Suspsense>

In this example, the Suspense boundary will load the child components, Header, NavBar etc. whilst loading we’ll see the fallback component. Upon completion that will be removed and we’ll see our full application.

But as mentioned, we need to lazy import these components so we’re have imports like this

const Header = lazy(()=> import("./components/Header"));
const NavBar = lazy(()=> import("./components/NavBar"));
const Content = lazy(()=> import("./components/Content"));
const StatusBar = lazy(()=> import("./components/StatusBar"));

There’s a requirement for lazy imported code to exported as default. If you don’t want to or cannot change the export to default (for example if using micro frontends that you didn’t write) then we can get around this with slightly more verbose syntax.

const Header = React.lazy(() => import("header/Header")
   .then(module => ({ default: module.Header })));

lazy is returning a promise, we can use the continuation as shown above (the then clause), to assign the module’s export to the default: parameter.

Along with use of Suspense to load the children whilst display a fallback, lazy also allows components to be selectively loaded, i.e. not everything is loaded at once, which has obvious results in ensuring better performance when not all components need loading initially, so using Suspense we also now have a nice way to load parts of our application as and when required as distinct parts of an application, each with a fallback. With lazy we also have more options for code splitting to reduce bundle size etc.

Micro frontends using React and Module Federation

Module federation allows us to create micro frontends in a pretty simple way.

Start off by running

npx create-mf-app

You’ll be prompted for a few things (these may change in different versions of this app. so these are current settings)

  • Pick the name of your app. This is essentially the name of the shell/host or main application (however you prefer to think of it). So I’m used to Shell application in MAUI, Xamarin Forms, WPF etc. So mine’s going to be named shell.
  • Project Type. We’re going to choose Application (as this is our shell application)
  • Port number. The default is 8080, but I’m going to choose 3000 (as I already have servers running on 8080 for other things).
  • Framework. We’re going to be using react
  • Language. I’m a TypeScript person, so I’m choosing typescript
  • CSS Accept the default which is CSS

Now our shell will be created. So as the prompt will state

  • cd shell
  • npm install
  • npm start

If all worked you’ll be presented by some large text stating the name of the app, framework etc.

Let’s now create another app in exactly the same way, obviously give it a different name (mine’s about), this is one of your micro frontends, also give it a different port, i.e. 8081 if you stuck with the default 8080 for the shell or in my case 3001.

Now go to that application and run it up using the commands below (again)

  • cd about
  • npm install
  • npm start

If all went well we have an application (the shell) running and the about application running as well. So nothing too exciting to see here, but it just goes to show we’re developing “standard” React apps.

What we will now need to do, is create a component that we’re going to expose from this application. All I’m going to do is move the App.tsx code that shows the application name etc. into a component, so if you want to follow along, let’s add a components folder to the src folder and within that add a About.tsx file and moved the code from the App.tsx into it, so it looks like this

import React from "react";

export const About = () => 
(
    <div className="container">
    <div>Name: about</div>
    <div>Framework: react</div>
    <div>Language: TypeScript</div>
    <div>CSS: Empty CSS</div>
  </div>
);

Now the App.tsx looks like this

import { About } from "./components/About";

const App = () => (
  <About />
);

We need to make some changes to webpack.config.js, so locate the ModuleFederationPlugin section.

  • Locate the exposes section and change it to look like this
    exposes: {
      "./About": "./src/components/About"
    },
    

    We expose all the components we want via this section and the shell app can then access them.

  • In your shell/container application we also need to amend the webpack.config.js and locate the remotes section, as you’ve probably worked out, this is going to register the micro frontends to be used within the shell/container. So mine looks like this

    remotes: {
      about: "about@http://localhost:3001/remoteEntry.js"
    },
    

    Let’s see if this work, open the shell/container’s App.tsx and add/change it to look like the below

    import { About } from 'about/About';
    
    const App = () => (
      <div> 
      <div className="container">
        <div>Name: shell</div>
        <div>Framework: react</div>
        <div>Language: TypeScript</div>
        <div>CSS: Empty CSS</div>
      </div>
      <About />
      </div>
    );
    

    Run the about app and then the shell and if all went to plan you’ll see the “About” component in the shell.

    A little more in depth ModuleFederationPlugin

    This is really cool and we can see much of the work is done on the ModuleFederationPlugin, so let’s look a little more in depth into some of the key features

    • name is the name we’re giving to our application
    • library is used to determine how exposed code will be stored/retrieved
    • filename by default is remoteEntry.js, but we can name it whatever we want, for example remoteAbout.js for our about app
    • remotes, as we’ve seen these point to the “modules” we want to include and have the format
      "app-name": "name@remote-host/entryFilename.js"
      
    • exposes, as we’ve seen also the exposes is used to expose components etc. from our micro frontend, it has the format
      "name" : "location-within-app"
      
    • shared is used to share node libraries which the exposed module depends on.

    Code

    Checkout an example shell app with three Material UI/React micro frontend apps at microfrontend-react. Each app. uses Material UI and the shell app. brings them together to create the basics of an application with header, navigation bar and some content.

    Obviously there’s a lot more to do to have a truly interactive micro frontend based application, but it’s a starting point.

The IAM User on AWS

When you signed up for AWS you created a Root user account. However we really should create another user (even if they have root like permissions) to run our cloud account.

Why do we need this user if they’re basically admin? Well we can reduce permissions but also delete the user without affecting the root user which we cannot do this on.

Let’s create an IAM (Identify and Access Management) user for our development use which will basically have admin permissions but would not be the user we use four out applications, this is essentially a developer account.

How do we set up our development user?

  • Log into your AWS account as Root user
  • In this search bar type IAM
  • First we want to create a new group, so select Access management | User groups
    • Click Create group
    • Enter a name for the group, usually we’d probably have this name match the application that the group represents, so I’m going to do this for my unit conversion API app, hence my group is UnitConversionApiUsers for my unit conversions API
    • In the Attach permissions policies let’s give this group AdministratorAccess permissions
    • Now click the Create group button
  • You should be placed back on the User groups screen and see out new group with zero users. So now click the Access management | Users option on the left of the screen
    • Click the Create user button
    • Enter a name for the user then click Next
    • Leave the default Add user to group
    • Check/tick the group you added then click the Next button
    • Finally click Create user
  • From the users screen on the Security credentials tab I have also clicked Enable console access, I also check the User must create new password at next sign-in, but you can autogenerate or create a custom password to suite
  • Download the .csv file for later use, but don’t worry if you don’t it will contain User name, Password and Console sign-in URL so you can copy these if you prefer from the UI

Note that the console URL contains the account number, we can change this using the alias option, from the IAM dashboard select the Dashboard option. On the right of the screen you’ll see AWS Account and am option to Create an Account Alias, clicking this we can enter a name to replace the account number in the URL. You’ll see the Sign-in URL change to suit.

Try signing into AWS by using the console URL or it’s alias if you changed that and in my case I was prompted to change the password, so did that and was able to log in.

You’ll also want to go the the user that you created and click Create access key. When completed download the .csv and or copy the access id and secret key so you can used from the AWS CLI or IDE integration.

Collection Expressions in C# 12

C# 12 includes something called Collection Expressions. These offer more generic way to create our collections from array-like syntax.

Let’s look first at the old style creation of an array of integers

var array = new [] { 1, 2, 3 };

This is simple enough array is of type int[]?. This way of creation arrays is not going away, but what if we want to change the array to a different collection then we end up using collection initializers like this

var list = new List<int> { 1, 2, 3 };

There’s nothing much wrong with this, but essentially we’re sort of doing the same thing, just with different syntax.

Collection expressions now allow us to use syntax such as (below) to create our collection regardless of type

int[] array = [1, 2, 3 ];
List<int> list = [1, 2, 3 ];

On the surface this may not seem a big deal, but imagine you’ve a class that accepts an int[] and maybe you change the type to a List, passing the values via the collection expression [] syntax means that part of your code remains unchanged, it just remains as [1, 2, 3].

Along with this we get to use the spread operator .. for example

List<int> list = [1, 2, 3 ];
int[] array = [.. list];

In this example we’ve created a list then basically copied the items to the array, but a spread operator can be used to concatenate values (or collections), such as

int[] array = [-3, -2, -1, 0, .. list];

Creating your own collections to use collection expressions

For many of the original types, such as List<T> the collection expression code is built in. But newer collections and, if we want, our own collection can take advantage of this syntax by following a minimal set of rules.

All we need to do is create our collection type and add the CollectionBuilderAttribute to it like this

[CollectionBuilder(typeof(MyCollection), nameof(MyCollection.Create))]
public class MyCollection<T>
{
   // our code
}

Now this is not going to work, the typeof expects a non-generic type, so we create a simple non-generic version of this class to handle the creation of the generic version. Also notice the CollectionBuilder expects the name of the method to call and expects a method that takes a single parameter of type ReadOnlySpan and returns the collection type, now initialized, like this

public class MyCollection
{
  public static MyCollection<T> Create<T>(ReadOnlySpan<T> items)
  {
     // returns a MyCollection<T>
  }
}

Let’s look at potential bare minimum implementation of this collection type which can be used with the collection expression syntax. Notice we will also need to implement IEnumerable and/or IEnumerable<T>

[CollectionBuilder(typeof(MyCollection), nameof(MyCollection.Create))]
public class MyCollection<T> : IEnumerable<T>
{
  public static readonly MyCollection<T> Empty = new(Array.Empty<T>());

  private readonly List<T> _innerCollection;

  internal MyCollection(T[]? items)
  {
    _innerCollection = items == null ? new List<T>() : [..items];
  }

  public T this[int index] => _innerCollection[index];
  public IEnumerator<T> GetEnumerator() => _innerCollection.GetEnumerator();
  IEnumerator IEnumerable.GetEnumerator() => _innerCollection.GetEnumerator();
}

public class MyCollection
{
  public static MyCollection<T> Create<T>(ReadOnlySpan<T> items)
  {
    return items.IsEmpty ? 
      MyCollection<T>.Empty : 
      new MyCollection<T>(items.ToArray());
  }
}

Ofcourse this is a silly example as we’re not adding anything that the inner List<T> cannot supply, but you get the idea. Now we can use the collection expression syntax on our new collection type

MyCollection<int> collection = [1, 2, 6, 7];

Docker on Windows

Note: I’m going through draft posts that go back to 2014 and publishing where they still may have value. They may not be 100% upto date but better published late than never.

Up until recently I’ve been solely using Docker on Linux, but I’ve a need to use it with Windows now. I’ve installed Docker Desktop and set to Switch to Windows containers.

So first off we’re going to download a Windows image, whilst there are several variants, for server, nano windows, we’re going to go pretty much full blown using

docker pull mcr.microsoft.com/windows:2004

Note: It may take some time to download, it’s > 3GB

Now just to prove it worked let’s run it in interactive mode and starting the cmd prompt using

docker run -it mcr.microsoft.com/windows:2004 cmd.exe

Exit your image by typing exit followed by return.

I’m going to want to create a volume to save/share files with the Windows machine running the container, so let’s create a folder c:\shared and then copy or create a file in here, just to allow us to prove we can connect to it through the container, so mine’s predictably HelloWorld.txt, now run

docker run -it -v c:\shared:c:\shared mcr.microsoft.com/windows:2004 cmd.exe

Note: Change cmd.exe to powershell.exe if you prefer

I’m naming the folder in the image the same as the real machine’s folder, but you can change it to suit, just change the second path like this

docker run -it -v c:\shared:c:\shared2 mcr.microsoft.com/windows:2004 cmd.exe

and if you created a simple text file with a message in it, once we’re inside the container we can type

type HelloWorld.txt

from the c:\shared folder to see it it’s as expected.

Now I’m wanting to start to install applications into the container, but with no GUI we need “silent installs”

msiexec /i .\myapp.msi /QN /L*V install.log

C# interop with F#

Note: I’m going through draft posts that go back to 2014 and publishing where they still may have value. They may not be 100% upto date but better published late than never.

The intention of this post is to demonstrate how various bits of F# code are viewed from C# code. Obviously as both are .NET languages compiling to IL they can call one another’s code.

F# modules

Let’s start at the top, an F# module. So let’s look at a simple module

module MyModule

let squared x = x * x

The module will appears to C# as a static class and the function squared will become a static method, for example

public static class MyModule
{
  public static int squared(int x);
}

F# inferred the type to be an int

Ofcourse from C# we’ll call this function like any other static function

int result = MyModule.squared(4);

Null

The preference within F# is to use the Option type. But if you are working in C# and not wanting to include F# specific types you might prefer to still return a null. However if you are doing something like the following

match results with
| null -> null
| a -> new Thing(a)

This will fail to compiler with an error such as “The type ‘Thing’ does not have ‘null’ as a proper value.”

We can solve this by marking the Thing type with the attribute AllowNullLiteral, for example

[<AllowNullLiteral>]
type Thing() =
   // members etc.

or we might change the origina F# code to

match results with
| null -> Operators.Unchecked.defaultof<Scale>
| a -> new Thing(a)

Swift Protocols, Structs and Classes

Note: I’m going through draft posts that go back to 2014 and publishing where they still may have value. They may not be 100% upto date but better published late than never.

Introduction

Protocols can be thought of as similar to interfaces within languages such as C# and Java, hence are used to declare expectations for implementations and can be returned or accepted as params.

We declare a protocol in the following way

protocol Person {
  var firstName: String { get }
  var lastName: String { get }
  var age: Int { get }
}

So we can create our implementation of this procotol like this

struct PersonImpl : Person {
  var firstName: String
  var lastName: String
  var age: Int
}

Structs and Classes

Apple Swift developers seem to prefer value types over reference types, a struct is a value type and class a reference type. They both share many of the same functionality.

Structs do not require an initializer (constructor) as a default initializer is supplied automatically and will expect all non-optional properties to be supplied.

class PersonImpl : Person {
  var firstName: String
  var lastName: String
  var age: Int

  init(firstName: String, lastName: String, age: Int) {
    self.firstName = firstName
    self.lastName = lastName
    self.age = age
  }
}

As you can see, init is the name of the initializer or what we may view as a constructor. Classes may also have deinit is called when Swift deallocates an instance (struct’s do not have deinit).

Mutations

Where we expect changes to the internal data, in other words where a function mutates data we use the mutating keyword. If, for example, we add a protocol function

func incrementAge() -> Void // same as func incrementAge()

By default struct properties cannot be changed by instance method. In such cases we need to include the mutating key word, i.e.

mutating func incrementAge() -> Void

So a struct method implementation will now look like this

mutating func incrementAge() -> Void {
  age += 1
}

Within a class, we don’t need to mark the method as mutating.

Accessors

There are five access levels

  • Open: This allows proprties, methods, classes etc. to be accessible by anything that imports the module
  • Public: This allows proprties, methods, classes etc. to be accessible by anything that imports the module
  • Internal: This is the default access level and allows properties, methods, classes etc. to be accessible from the module where they’re defined only
  • Fileprivate: Properties and methods can access from code withint he same source file only
  • Private: The least visible accessor meaning properties methods etc. are only visible within the same file or class etc.

is and as

Swift has the concept of is and as pretty much like C#. So we can declare a type as follows

let p: Person = PersonImp("Scooby", "Doo", "12")

Now if we needed to check if the const p is a PersonImpl we would use

if p is PersonImpl {    
}

we could use as within the if statement if we wanted as well, for example

if let person = p as? PersonImpl {    
}

Here we compare and assign in the same line of code using as? which basically means the person may be nil (if the type cannot be converted to PersonImpl).

Extensions

Just like extension classes in C# – Swift extensions allow us to add functionality to structures, protocols and classes (as well as enumerations).

An extension is defined using the extension key word, for example

extension String {
  // add string functionality, for example
  func capitalize() -> String {
    return self[0].toUpper() + self.substr(1)
  }
}

We can also add constraints to extensions, so for example a Collection extension that

extension Collection where Iterator.Element: Comparable {
  // add functionality where elements support Comparable
}

Equatable

A type needs to conform to the Equatable protocol for a type to be comparable using == and !=.

Generics

Swift generics look much the same as generics in C++, Java and C# in that we can declare generics on a function like this

func add<T>(item: T) {
}

We can also add constraints to the genric type like this

func compare<T: Comparable>(a: T, b: T) -> Bool {
}

Experiments with Swift Generics

Note: I’m going through draft posts that go back to 2014 and publishing where they still may have value. They may not be 100% upto date but better published late than never.

Generics are interesting within Swift. Syntactically they look the same as most other languages which supports generics. So, we have the syntax below

class Policy<TResult> {
}

Generic and non-generic types of the same name

In C# we can essentially create a generic and non generic type and the compiler works out which we meant to use. Sadly this doesn’t exist in Swift (or Java or maybe many other implementations). But then, rather interestingly we can (assuming no constraint on the generic parameter) actually create a generic of type Void, i.e.

class Test<TResult> {
  func run(_ action: () -> TResult) -> TResult {
    return action()
  }
}

and then write code like this

let a1 = Test<String>()
let a2 = a1.run({ return "Hello" })

let b1 = Test<Void>()
let b2 = b1.run({ return })

What about, if we looked at this problem from the perspective of creating a default type for the generic. Swift doesn’t support this but we can (sort of) create something like this using init. Where we take an argument of the generic type, so for example if we have

class Test<T> {
  init(value: T) {
  }
}

Okay so we can now declare our variables like this

let t1 = Test(value: 42)
let t2 = Test(value: "Hello")

I know, this doesn’t have a default implementation at all, so instead we’ll create an extension which does the defaulting for us

extension Test where T == Person {
    convenience init() {
        self.init(value: Person())
    }
}

Now we create an instance like this

let t3 = Test()

See Can I assign a default type to generic type T in Swift? for further information on this one.

Adding constraints

We can add a constraint, for example the generic type must a a type of Error, like this

class Test<TError: Error> {
}

We can also create constraints against properties of a type, for example at a function level we can check or (as above) on extensions using the where clause. So in the example

extension Test where T == Person {
}

The extension implicitly knows that there’s a generic parameter T and so we don’t need to redeclare it, instead we’re simply saying this extension works for Test where T is a Person. This sort of technique can also be used within functions.

Default value

If you come from a C# background, you’ll probably be used to using default(T) or the shortened version simply default. Swift doesn’t include such a keyword. Instead you’ll either need to use optionals, i.e. returned a .some or .none, or ofcourse you might create a function of class to imitate such functionality yourself.

Swift async/await

Note: I’m going through draft posts that go back to 2014 and publishing where they still may have value. They may not be 100% upto date but better published late than never.

Swift comes with async/await syntax/functionality, however the support is dependent upon version of Swift and more importantly whether it’s the Mac (I’ll include iOS and watch OS in this) version of the Linux version.

Currently I’m playing with Swift on Linux and using the URLSession.shared.dataTask which is essentially a callback function. The URLSession.shared.data async/await version currently does not exist on Linux. So let’s create our own version and look at the process and ofcourse, how to use async/await (if you’ve used C#, TypeScript with async/await you’ll already know the basics of using the syntax).

Functions should be declared as async, so for example

func data(for request: URLRequest) async -> (Data, URLResponse) {
  // do something
}

Here’s an example of using the URLSession.shared.dataTask and declaring the code in an async method

#if canImport(FoundationNetworking)
public extension URLSession {
    func data(for request: URLRequest) async -> (Data, URLResponse) {
        await withCheckedContinuation { continuation in
            URLSession.shared.dataTask(with: request) { data, response, error in
                continuation.resume(returning: (data!, response!))
            }.resume()
        }
    }
}
#endif

Now, in usage, we can do this

let data = await URLSession.shared.data(for: request)