This page looks best with JavaScript enabled

NextJS 13 is a good step forward

 ·   ·  ☕ 8 min read

I am quite excited about the possibilities offered by NextJS 13. Announced in Oct ‘22, the most popular JavaScript framework has set fantastic standards for rest of the world.

I particularly liked the below features -

  • React Server Components that makes life simpler, but the coding process is much more efficient
  • The new directory structure that simplifies routes. Layouts are intuitive and simple
  • Love the new links (yes, I had to say this)

What lays hidden in the first statement is all the power hidden in React Server Component itself and that of streaming SSR. Combined with the new way of writing pages, I think this came closest for me to reconsider coding in React.

  • Write server and client code in context of a component
  • Do not worry about writing an entire backend infrastructure
  • Smaller JS download sizes
  • Execute faster with streaming SSR - UI that loads instantly and maintains performance for all the magical stuff in your app

With easy deployment options and the ability to serve many things from edge, NextJS is compelling for any new apps in the React world.

Time for a To-do app

Let us try out the new features with a simple to-do app written with -

  1. NextJS
  2. Pico CSS for styling
  3. PocketBase for backend

Setup

Create the NextJS app.

1
2
cd c:\dev\myprojects
pnpm create next-app todo-next13 --experimental-app

You may not want the experimental flag once the “app” directory and associated functions are main stream.

Install Pico CSS

You can just CDN into Pico CSS, a lightweight, easy-to-use CSS library that is perfect for experimental projects.

I just installed it for later use..

1
pnpm install pico-css
Setup PocketBase

PocketBase is turning out to be a God send for me to quickly try out applications with a robust backend. PocketBase is written in Go, is extensible since it can also act as a framework, and has a good database (uses SQLite).

As the first step, download PocketBase executable. Store the exe in <_root-folder_ > db.

You can serve PocketBase with -

1
./pocketbase serve

You can access the database using automatic REST APIs, or use the JS SDK. We will use the SDK since we never know when our To do app will blow up.

1
pnpm install pocketbase

Define data model in PocketBase

Once installed, you should be able to access the admin UI to create tables and enter dummy records in the database before you take your next gulp of your favourite drink.

Point your browser to http://127.0.0.1:8090/_/.

Click on them shiny buttons to create a table and a few columns.

Code the Todo app

It is time to write some magic.

Create layout and home page

In the _root-folder_ > app directory create a new file called ‘layout.tsx’.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

import Link from "next/link";
import "@picocss/pico/css/pico.min.css";
import "../app/css/custom.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <head>
      
      </head>
      <body>
        <main className="container">

        <nav>
          <Link href="/">Home</Link>
          <Link href="/todos">Todo</Link>
        </nav>
        {children}
        </main>
      
      
        </body>
    </html>
  );
}

layout.tsx will serve as our home page container. You can code in-

  1. All the elements that are common across components - e.g. Nav, etc.
  2. Include CSS - Pico CSS and any custom CSS that you fancy

Don’t forget to create custom.css in css directory if you have included a custom CSS statement.

Create another file called page.tsx, which will serve as the root page of your app.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import Link from "next/link";

export default function HomePage() {
  return (
    <div>
      <h1>Home</h1>
      <p>Awesome Next Todo</p>
      <div className="padding-top: 3em">
        <Link href="/todos">View Todos</Link>
      </div>
    </div>
  );
}

Execute your Next app if you are have not already done so and see your beautiful app..

1
pnpm run dev

todo-app-nextjs13-pico-css-pocketbase

You are now ready to add the core functionality.

Create a folder called todos in _root-folder.

Add a new files called page.tsx..

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
export const fetchCache = false;
import Link from "next/link";
import PocketBase from "pocketbase";
import CreateTodo from "./CreateTodo";

const client = new PocketBase("http://127.0.0.1:8090");

async function getTodos(fromRec: number = 1, filter = "") {
  const res = await client.records.getList("todos", fromRec, 20, {
    filter: filter,
  });

  return res?.items as any[];
}
export default async function TodosPage() {
  const todos = await getTodos();

  return (
    <div>
      <h1>Todos</h1>

      {todos?.map((todo) => (
        <Todo key={todo.id} todo={todo}></Todo>
      ))}
      <div className="padding-top: 1em;padding-bottom: 1em;">
        <CreateTodo></CreateTodo>
      </div>
    </div>
  );
}

function Todo({ todo }: any) {
  return (
    <Link href={`/todos/${todo.id}`}>
      <div className="grid">
        <div className="todo-status">
          <input
            type="checkbox"
            name="status"
            id="status"
            checked={todo.status == "Complete"}
            value="Complete"
          />
        </div>

        <div className="todo-desc">{todo.description} </div>
        <div className="todo-date">
          <input
            name="todo-date"
            id="todo-date"
            className="todo-date-input"
            value={todo.planned_date}
            readOnly={todo.status == "Complete"}
          />
        </div>
      </div>
    </Link>
  );
}

page.tsx is the root page when you navigate to localhost:3000/todos. There is simple code here to -

  1. Query todos from database
  2. Display the todos
  3. Enable creation of new todo through the CreateTodo component (which we will create next)

Create another file called CreateTodo.tsx in the same folder -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
"use client";

import Link from "next/link";
import { useState } from "react";
import PocketBase from "pocketbase";
import { useRouter } from "next/navigation";

const client = new PocketBase("http://127.0.0.1:8090");

export default function CreateNote() {
  const [desc, setDesc] = useState("");
  const [status, setStatus] = useState("");
  const [plannedDate, setPlannedDate] = useState("");

  const router = useRouter();

  const createTodo = async () => {
    await client.records.create("todos", {
      description: desc,
      status: status,
    });
    setStatus("");
    setDesc("");
    router.refresh();
  };
  return (
    <div>
      <form onSubmit={createTodo}>
        <div>
          <i className="margin-bottom: 1em;">Create New Todo..</i>

          <label htmlFor="desc">Description</label>
          <input
            name="desc"
            value={desc}
            onChange={(e) => setDesc(e.target.value)}></input>

          <label htmlFor="status">Status</label>
          <input
            name="status"
            value={status}
            onChange={(e) => setStatus(e.target.value)}></input>
          <button type="submit">Create Todo</button>
        </div>
      </form>
    </div>
  );
}

use client denotes that the CreateTodo component needs to be interactive on the client. All the earlier components can execute code on server and send the results to the client, while CreateTodo has to be pushed as Javascript to client and the form needs to accept user inputs.

Other than the client interaction, CreateTodo is ‘more or less’ standard React.

The code described so far should be good enough to complete all the things that the app needs to do. However, let us add a dynamic route just because we can. (Also, this will eliminate the errors if you have copied the code as is).

Create a directory [id] under todos directory. Create a new file page.tsx -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import Link from "next/link";
import PocketBase from "pocketbase";

const client = new PocketBase("http://127.0.0.1:8090");

async function getTodo(todoId: string = "") {
  const res = todoId
    ? await client.records.getOne("todos", todoId)
    : { id: "", description: "", status: "" };

  return res;
}
export default async function TodoPage({ params }: any) {
  const todo = await getTodo(params.id);
  return (
    <div>
      <h1>Todo</h1>
      <div className="padding-bottom: 1em;">
        <Link href="/todos">Back to todos</Link>
      </div>

      <label htmlFor="id">Id</label>
      <input name="id" value={todo.id}></input>

      <label htmlFor="desc">Description</label>
      <input name="desc" value={todo.description}></input>

      <label htmlFor="status">Status</label>
      <input name="status" value={todo.status}></input>
    </div>
  );
}

You would have probably logged out by now, or know the drill..

  1. page.tsx is the root page
  2. The component accepts a parameter from the caller to query for a particular todo record
  3. Display the todo

What is different is the [id] tag that indicates that the component is dynamic (spare me the suprise look).

Lo & behold, you now have a to do app that can make the next million dollar SaaS app.

See complete code for this incomplete app on GitHub.

Why I will not switch to NextJS

I really like where NextJS is going. I see compelling reasons for using Next - the tooling, ease of deployment, and the massive community that tells me how my code is completely wrong and how I should be thinking about solving world hunger.

But again, ReactJS is not exactly my favourite way to write apps (yes, that’s me being stupid). Also -

  • I still think ReactJS is a lot of boilerplate
  • I don’t quite enjoy writing a lot of code to maintain state, render a list or do the thousand things that every web app has to do
  • I don’t have to answer to anyone or collaborate with 100 people for my side projects
  • I don’t have a massive library of UI components built out in React that can help me speed up the development process

Till I can check at least three of the above boxes, I will continue to code in either Vue or Svelte.

Stay in touch!
Share on

Prashanth Krishnamurthy
WRITTEN BY
Prashanth Krishnamurthy
Technologist | Creator of Things