Skip to content

Angular 17: Continuing the Renaissance

Angular 17: Continuing the Renaissance

Angular 17: A New Era

November 8th marked a significant milestone in the world of Angular with the release of Angular 17. This wasn't just any ordinary update; it was a leap forward, signifying a new chapter for the popular framework. But what made this release truly stand out was the unveiling of Angular's revamped website, complete with a fresh brand identity and a new logo. This significant transformation represents the evolving nature of Angular, aligning with the modern demands of web development.

Angular's New Logo

To commemorate this launch, we also hosted a release afterparty, where we went deep into its new features with Minko Gechev from the Angular core team, and Google Developer Experts (GDEs) Brandon Roberts, Deborah Kurata, and Enea Jahollari.

But what exactly are these notable new features in the latest version? Let's dive in and explore.

The Angular Renaissance

Angular has been undergoing a significant revival, often referred to as Angular's renaissance, a term coined by Sarah Drasner, the Director of Engineering at Google, earlier this year. This revival has been particularly evident in its recent versions. The Angular team has worked hard to introduce many new improvements, focusing on signal-based reactivity, hydration, server-side rendering, standalone components, and migrating to esbuild and Vite for a better and faster developer experience. This latest release, in particular, marks many of these features as production-ready.

Standalone Components

About a year ago, Angular began a journey toward modernity with the introduction of standalone components. This move significantly enhanced the developer experience, making Angular more contemporary and user-friendly.

In Angular's context, a standalone component is a self-sufficient, reusable code unit that combines logic, data, and user interface elements. What sets these components apart is their independence from Angular's NgModule system, meaning they do not rely on it for configuration or dependencies. By setting a standalone: true flag, you no longer need to embed your component in an NgModule and you can bootstrap directly off that component:

// ./app/app.component.ts
@Component({ selector: 'app', template: 'hello', standalone: true })
export class AppComponent {}

// ./main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent).catch(e => console.error(e));

Compared to the NgModules way of adding components, as shown below, you can immediately see how standalone components make things much simpler.

// ./app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'CodeSandbox';
}

// ./app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

// .main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch((err) => console.error(err));

In this latest release, the Angular CLI now defaults to generating standalone components, directives, and pipes. This default setting underscores the shift towards a standalone-centric development approach in Angular.

New Syntax for Enhanced Control Flow

Angular 17 introduces a new syntax for control flow, replacing traditional structural directives like ngIf or ngFor, which have been part of Angular since version 2. This new syntax is designed for fine-grained change detection and eventual zone-less operation when Angular completely migrates to signals. It's more streamlined and performance-efficient, making handling conditional or list content in templates easier.

The @if block replaces *ngIf for expressing conditional parts of the UI.

@if (a > b) {
  {{a}} is greater than {{b}}
} @else if (b > a) {
  {{a}} is less than {{b}}
} @else {
  {{a}} is equal to {{b}}
}

The @switch block replaces ngSwitch, offering benefits such as not requiring a container element to hold the condition expression or each conditional template. It also supports template type-checking, including type narrowing within each branch.

@switch (condition) {
  @case (caseA) {
    Case A.
  }
  @case (caseB) {
    Case B.
  }
  @default {
    Default case.
  }
}

The @for block replaces *ngFor for iteration and presents several differences compared to its structural directive predecessor, ngFor. For example, the tracking expression (calculating keys corresponding to object identities) is mandatory but offers better ergonomics. Additionally, it supports @empty blocks.

@for (item of items; track item.id) {
  {{ item.name }}
}

Defer Block for Lazy Loading

Angular 17 introduces the @defer block, a dramatically improving lazy loading of content within Angular applications. Within the @defer block framework, several sub-blocks are designed to elegantly manage different phases of the deferred loading process. The main content within the @defer block is the segment designated for lazy loading. Initially, this content is not rendered, becoming visible only when specific triggers are activated or conditions are met, and after the required dependencies have been loaded. By default, the trigger for a @defer block is the browser reaching an idle state.

For instance, take the following block: it delays the loading of the calendar-imp component until it comes into the viewport. Until that happens, a placeholder is shown. This placeholder displays a loading message when the calendar-imp component begins to load, and an error message if, for some reason, the component fails to load.

@defer (on viewport) {
  <calendar-cmp />
} @placeholder {
  <div>Calendar placeholder</div>
} @loading {
  <div>Loading calendar</div>
} @error {
  <div>Error loading calendar</div>
}

The on keyword supports a wide a variety of other conditions, such as:

  • idle (when the browser has reached an idle state)
  • interaction (when the user interacts with a specified element)
  • hover (when the mouse has hovered over a trigger area)
  • timer(x) (triggers after a specified duration)
  • immediate (triggers the deferred load immediately)

The second option of configuring when deferring happens is by using the when keyword. For example:

@defer (when isVisible) {
  <calendar-cmp />
}

Server-Side Rendering (SSR)

Angular 17 has made server-side rendering (SSR) much more straightforward. Now, a --ssr option is included in the ng new command, removing the need for additional setup or configurations. When creating a new project with the ng new command, the CLI inquires if SSR should be enabled. As of version 17, the default response is set to 'No'. However, for version 18 and beyond, the plan is to enable SSR by default in newly generated applications. If you prefer to start with SSR right away, you can do so by initializing your project with the --ssr flag:

ng new --ssr

For adding SSR to an already existing project, utilize the ng add command of the Angular CLI:

ng add @angular/ssr

Hydration

In Angular 17, the process of hydration, which is essential for reviving a server-side rendered application on the client-side, has reached a stable, production-ready status. Hydration involves reusing the DOM structures rendered on the server, preserving the application's state, and transferring data retrieved from the server, among other crucial tasks. This functionality is automatically activated when server-side rendering (SSR) is used. It offers a more efficient approach than the previous method, where the server-rendered tree was completely replaced, often causing visible UI flickers. Such re-rendering can adversely affect Core Web Vitals, including Largest Contentful Paint (LCP), leading to layout shifts. By enabling hydration, Angular 17 allows for the reuse of the existing DOM, effectively preventing these flickers.

Support for View Transitions

The new View Transitions API, supported by some browsers, is now integrated into the Angular router. This feature, which must be activated using the withViewTransitions function, allows for CSS-based animations during route transitions, adding a layer of visual appeal to applications.

To use it, first you need to import withViewTransitions:

import { provideRouter, withViewTransitions } from '@angular/router';

Then, you need to add it to the provideRouter configuration:

bootstrapApplication(AppComponent, {
 providers: [
   provideRouter(routes, withViewTransitions())
 ]
})

Other Notable Changes

  • Angular 17 has stabilized signals, initially introduced in Angular 16, providing a new method for state management in Angular apps.
  • Angular 17 no longer supports Node 16. The minimal Node version required is now 18.13.
  • TypeScript version 5.2 is the least supported version starting from this release of Angular.
  • The @Component decorator now supports a styleUrl attribute. This allows for specifying a single stylesheet path as a string, simplifying the process of linking a component to a specific style sheet. Previously, even for a single stylesheet, an array was required under styleUrls.

Conclusion

With the launch of Angular 17, the Angular Renaissance is now in full swing. This release has garnered such positive feedback that developers are showing renewed interest in the framework and are looking forward to leveraging it in upcoming projects. However, it's important to note that it might take some time for IDEs to adapt to the new templating syntax fully. While this transition is underway, rest assured that you can still write perfectly valid code using the old templating syntax, as all the changes in Angular 17 are backward compatible.

Looking ahead, the future of Angular appears brighter than ever, and we can't wait to see what the next release has in store!

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

Testing a Fastify app with the NodeJS test runner cover image

Testing a Fastify app with the NodeJS test runner

Introduction Node.js has shipped a built-in test runner for a couple of major versions. Since its release I haven’t heard much about it so I decided to try it out on a simple Fastify API server application that I was working on. It turns out, it’s pretty good! It’s also really nice to start testing a node application without dealing with the hassle of installing some additional dependencies and managing more configurations. Since it’s got my stamp of approval, why not write a post about it? In this post, we will hit the highlights of the testing API and write some basic but real-life tests for an API server. This server will be built with Fastify, a plugin-centric API framework. They have some good documentation on testing that should make this pretty easy. We’ll also add a SQL driver for the plugin we will test. Setup Let's set up our simple API server by creating a new project, adding our dependencies, and creating some files. Ensure you’re running node v20 or greater (Test runner is a stable API as of the 20 major releases) Overview `index.js` - node entry that initializes our Fastify app and listens for incoming http requests on port 3001 `app.js` - this file exports a function that creates and returns our Fastify application instance `sql-plugin.js` - a Fastify plugin that sets up and connects to a SQL driver and makes it available on our app instance Application Code A simple first test For our first test we will just test our servers index route. If you recall from the app.js` code above, our index route returns a 501 response for “not implemented”. In this test, we're using the createApp` function to create a new instance of our Fastify app, and then using the `inject` method from the Fastify API to make a request to the `/` route. We import our test utilities directly from the node. Notice we can pass async functions to our test to use async/await. Node’s assert API has been around for a long time, this is what we are using to make our test assertions. To run this test, we can use the following command: By default the Node.js test runner uses the TAP reporter. You can configure it using other reporters or even create your own custom reporters for it to use. Testing our SQL plugin Next, let's take a look at how to test our Fastify Postgres plugin. This one is a bit more involved and gives us an opportunity to use more of the test runner features. In this example, we are using a feature called Subtests. This simply means when nested tests inside of a top-level test. In our top-level test call, we get a test parameter t` that we call methods on in our nested test structure. In this example, we use `t.beforeEach` to create a new Fastify app instance for each test, and call the `test` method to register our nested tests. Along with `beforeEach` the other methods you might expect are also available: `afterEach`, `before`, `after`. Since we don’t want to connect to our Postgres database in our tests, we are using the available Mocking API to mock out the client. This was the API that I was most excited to see included in the Node Test Runner. After the basics, you almost always need to mock some functions, methods, or libraries in your tests. After trying this feature, it works easily and as expected, I was confident that I could get pretty far testing with the new Node.js core API’s. Since my plugin only uses the end method of the Postgres driver, it’s the only method I provide a mock function for. Our second test confirms that it gets called when our Fastify server is shutting down. Additional features A lot of other features that are common in other popular testing frameworks are also available. Test styles and methods Along with our basic test` based tests we used for our Fastify plugins - `test` also includes `skip`, `todo`, and `only` methods. They are for what you would expect based on the names, skipping or only running certain tests, and work-in-progress tests. If you prefer, you also have the option of using the describe` → `it` test syntax. They both come with the same methods as `test` and I think it really comes down to a matter of personal preference. Test coverage This might be the deal breaker for some since this feature is still experimental. As popular as test coverage reporting is, I expect this API to be finalized and become stable in an upcoming version. Since this isn’t something that’s being shipped for the end user though, I say go for it. What’s the worst that could happen really? Other CLI flags —watch` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--watch —test-name-pattern` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--test-name-pattern TypeScript support You can use a loader like you would for a regular node application to execute TypeScript files. Some popular examples are tsx` and `ts-node`. In practice, I found that this currently doesn’t work well since the test runner only looks for JS file types. After digging in I found that they added support to locate your test files via a glob string but it won’t be available until the next major version release. Conclusion The built-in test runner is a lot more comprehensive than I expected it to be. I was able to easily write some real-world tests for my application. If you don’t mind some of the features like coverage reporting being experimental, you can get pretty far without installing any additional dependencies. The biggest deal breaker on many projects at this point, in my opinion, is the lack of straightforward TypeScript support. This is the test command that I ended up with in my application: I’ll be honest, I stole this from a GitHub issue thread and I don’t know exactly how it works (but it does). If TypeScript is a requirement, maybe stick with Jest or Vitest for now 🙂...

Understanding Vue's Reactive Data cover image

Understanding Vue's Reactive Data

Introduction Web development has always been about creating dynamic experiences. One of the biggest challenges developers face is managing how data changes over time and reflecting these changes in the UI promptly and accurately. This is where Vue.js, one of the most popular JavaScript frameworks, excels with its powerful reactive data system. In this article, we dig into the heart of Vue's reactivity system. We unravel how it perfectly syncs your application UI with the underlying data state, allowing for a seamless user experience. Whether new to Vue or looking to deepen your understanding, this guide will provide a clear and concise overview of Vue's reactivity, empowering you to build more efficient and responsive Vue 3 applications. So, let’s kick off and embark on this journey to decode Vue's reactive data system. What is Vue's Reactive Data? What does it mean for data to be ”'reactive”? In essence, when data is reactive, it means that every time the data changes, all parts of the UI that rely on this data automatically update to reflect these changes. This ensures that the user is always looking at the most current state of the application. At its core, Vue's Reactive Data is like a superpower for your application data. Think of it like a mirror - whatever changes you make in your data, the user interface (UI) reflects these changes instantly, like a mirror reflecting your image. This automatic update feature is what we refer to as “reactivity”. To visualize this concept, let's use an example of a simple Vue application displaying a message on the screen: `javascript import { createApp, reactive } from 'vue'; const app = createApp({ setup() { const state = reactive({ message: 'Hello Vue!' }); return { state }; } }); app.mount('#app'); ` In this application, 'message' is a piece of data that says 'Hello Vue!'. Let's say you change this message to 'Goodbye Vue!' later in your code, like when a button is clicked. `javascript state.message = 'Goodbye Vue!'; ` With Vue's reactivity, when you change your data, the UI automatically updates to 'Goodbye Vue!' instead of 'Hello Vue!'. You don't have to write extra code to make this update happen - Vue's Reactive Data system takes care of it. How does it work? Let's keep the mirror example going. Vue's Reactive Data is the mirror that reflects your data changes in the UI. But how does this mirror know when and what to reflect? That's where Vue's underlying mechanism comes into play. Vue has a behind-the-scenes mechanism that helps it stay alerted to any changes in your data. When you create a reactive data object, Vue doesn't just leave it as it is. Instead, it sends this data object through a transformation process and wraps it up in a Proxy. Proxy objects are powerful and can detect when a property is changed, updated, or deleted. Let's use our previous example: `javascript import { createApp, reactive } from 'vue'; const app = createApp({ setup() { const state = reactive({ message: 'Hello Vue!' }); return { state }; } }); app.mount('#app'); ` Consider our “message” data as a book in a library. Vue places this book (our data) within a special book cover (the Proxy). This book cover is unique - it's embedded with a tracking device that notifies Vue every time someone reads the book (accesses the data) or annotates a page (changes the data). In our example, the reactive function creates a Proxy object that wraps around our state object. When you change the 'message': `javascript state.message = 'Goodbye Vue!'; ` The Proxy notices this (like a built-in alarm going off) and alerts Vue that something has changed. Vue then updates the UI to reflect this change. Let’s look deeper into what Vue is doing for us and how it transforms our object into a Proxy object. You don't have to worry about creating or managing the Proxy; Vue handles everything. `javascript const state = reactive({ message: 'Hello Vue!' }); // What vue is doing behind the scenes: function reactive(obj) { return new Proxy(obj, { // target = state and key = message get(target, key) { track(target, key) return target[key] }, set(target, key, value) { target[key] = value // Here Vue will trigger its reactivity system to update the DOM. trigger(target, key) } }) } ` In the example above, we encapsulate our object, in this case, “state”, converting it into a Proxy object. Note that within the second argument of the Proxy, we have two methods: a getter and a setter. The getter method is straightforward: it merely returns the value, which in this instance is “state.message” equating to 'Hello Vue!' Meanwhile, the setter method comes into play when a new value is assigned, as in the case of “state.message = ‘Hey young padawan!’”. Here, “value” becomes our new 'Hey young padawan!', prompting the property to update. This action, in turn, triggers the reactivity system, which subsequently updates the DOM. Venturing Further into the Depths If you have been paying attention to our examples above, you might have noticed that inside the Proxy` method, we call the functions `track` and `trigger` to run our reactivity. Let’s try to understand a bit more about them. You see, Vue 3 reactivity data is more about Proxy objects. Let’s create a new example: `vue import { reactive, watch, computed, effect } from "vue"; const state = reactive({ showSword: false, message: "Hey young padawn!", }); function changeMessage() { state.message = "It's dangerous to go alone! Take this."; } effect(() => { if (state.message === "It's dangerous to go alone! Take this.") { state.showSword = true; } }); {{ state.message }} Click! ` In this example, when you click on the button, the message's value changes. This change triggers the effect function to run, as it's actively listening for any changes in its dependencies__. How does the effect` property know when to be called? Vue 3 has three main functions to run our reactivity: effect`, `track`, and `trigger`. The effect` function is like our supervisor. It steps in and takes action when our data changes – similar to our effect method, we will dive in more later. Next, we have the track` function. It notes down all the important data we need to keep an eye on. In our case, this data would be `state.message`. Lastly, we've got the trigger` function. This one is like our alarm bell. It alerts the `effect` function whenever our important data (the stuff `track` is keeping an eye on) changes. In this way, trigger`, `track`, and `effect` work together to keep our Vue application reacting smoothly to changes in data. Let’s go back to them: `javascript function reactive(obj) { return new Proxy(obj, { get(target, key) { // target = state & key = message track(target, key) // keep an eye for this return target[key] }, set(target, key, value) { target[key] = value trigger(target, key) // trigger the effects! } }) } ` Tracking (Dependency Collection) Tracking is the process of registering dependencies between reactive objects and the effects that depend on them. When a reactive property is read, it's "tracked" as a dependency of the current running effect. When we execute track()`, we essentially store our effects in a Set object. But what exactly is an "effect"? If we revisit our previous example, we see that the effect method must be run whenever any property changes. This action — running the effect method in response to property changes — is what we refer to as an "Effect"! (computed property, watcher, etc.) > Note: We'll outline a basic, high-level overview of what might happen under the hood. Please note that the actual implementation is more complex and optimized, but this should give you an idea of how it works. Let’s see how it works! In our example, we have the following reactive object: `javascript const state = reactive({ showSword: false, message: "Hey young padawn!", }); // which is transformed under the hood to: function reactive(obj) { return new Proxy(obj, { get(target, key) { // target = state | key = message track(target, key) // keep an eye for this return target[key] }, set(target, key, value) { target[key] = value trigger(target, key) // trigger the effects! } }) } ` We need a way to reference the reactive object with its effects. For that, we use a WeakMap. Which type is going to look something like this: `typescript WeakMap>> ` We are using a WeakMap to set our object state as the target (or key). In the Vue code, they call this object `targetMap`. Within this targetMap` object, our value is an object named `depMap` of Map type. Here, the keys represent our properties (in our case, that would be `message` and `showSword`), and the values correspond to their effects – remember, they are stored in a Set that in Vue 3 we refer to as `dep`. Huh… It might seem a bit complex, right? Let's make it more straightforward with a visual example: With the above explained, let’s see what this Track` method kind of looks like and how it uses this `targetMap`. This method essentially is doing something like this: `javascript let activeEffect; // we will see more of this later function track(target, key) { if (activeEffect) { // depsMap` maps targets to their keys and dependent effects let depsMap = targetMap.get(target); // If we don't have a depsMap for this target in our targetMap`, create one. if (!depsMap) { depsMap = new Map(); targetMap.set(target, depsMap); } let dep = depsMap.get(key); if (!dep) { // If we don't have a set of effects for this key in our depsMap`, create one. dep = new Set(); depsMap.set(key, dep); } // Add the current effect as a dependency dep.add(activeEffect); } } ` At this point, you have to be wondering, how does Vue 3 know what activeEffect` should run? Vue 3 keeps track of the currently running effect by using a global variable. When an effect is executed, Vue temporarily stores a reference to it in this global variable, allowing the track` function to access the currently running effect and associate it with the accessed reactive property. This global variable is called inside Vue as `activeEffect`. Vue 3 knows which effect is assigned to this global variable by wrapping the effects functions in a method that invokes the effect whenever a dependency changes. And yes, you guessed, that method is our effect` method. `javascript effect(() => { if (state.message === "It's dangerous to go alone! Take this.") { state.showSword = true; } }); ` This method behind the scenes is doing something similar to this: `javascript function effect(update) { //the function we are passing in const effectMethod = () => { // Assign the effect as our activeEffect` activeEffect = effectMethod // Runs the actual method, also triggering the get` trap inside our proxy update(); // Clean the activeEffect after our Effect has finished activeEffect = null } effectMethod() } ` The handling of activeEffect` within Vue's reactivity system is a dance of careful timing, scoping, and context preservation. Let’s go step by step on how this is working all together. When we run our `Effect` method for the first time, we call the `get` trap of the Proxy. `javascript function effect(update) const effectMethod = () => { // Storing our active effect activeEffect = effectMethod // Running the effect update() ... } ... } effect(() => { // we call the the get` trap when getting our `state.message` if (state.message === "It's dangerous to go alone! Take this.") { state.showSword = true; } }); ` When running the get` trap, we have our `activeEffect` so we can store it as a dependency. `javascript function reactive(obj) { return new Proxy(obj, { // Gets called when our effect runs get(target, key) { track(target, key) // Saves the effect return target[key] }, // ... (other handlers) }) } function track(target, key) { if (activeEffect) { //... rest of the code // Add the current effect as a dependency dep.add(activeEffect); } } ` This coordination ensures that when a reactive property is accessed within an effect, the track function knows which effect is responsible for that access. Trigger Method Our last method makes this Reactive system to be complete. The trigger` method looks up the dependencies for the given target and key and re-runs all dependent effects. `javascript function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; // no dependencies, no effects, no need to do anything const dep = depsMap.get(key); if (!dep) return; // no dependencies for this key, no need to do anything // all dependent effects to be re-run dep.forEach(effect => { effect() }); } ` Conclusion Diving into Vue 3's reactivity system has been like unlocking a hidden superpower in my web development toolkit, and honestly, I've had a blast learning about it. From the rudimentary elements of reactive data and instantaneous UI updates to the intricate details involving Proxies, track and trigger functions, and effects, Vue 3's reactivity is an impressively robust framework for building dynamic and responsive applications. In our journey through Vue 3's reactivity, we've uncovered how this framework ensures real-time and precise updates to the UI. We've delved into the use of Proxies to intercept and monitor variable changes and dissected the roles of track and trigger functions, along with the 'effect' method, in facilitating seamless UI updates. Along the way, we've also discovered how Vue ingeniously manages data dependencies through sophisticated data structures like WeakMaps and Sets, offering us a glimpse into its efficient approach to change detection and UI rendering. Whether you're just starting with Vue 3 or an experienced developer looking to level up, understanding this reactivity system is a game-changer. It doesn't just streamline the development process; it enables you to create more interactive, scalable, and maintainable applications. I love Vue 3, and mastering its reactivity system has been enlightening and fun. Thanks for reading, and as always, happy coding!...

Next.js Authentication Using OAuth cover image

Next.js Authentication Using OAuth

Modern web apps have come a long way from their early days, and as a result, users have come to expect certain features. One such feature is being able to authenticate in the web app using external accounts owned by providers such as Facebook, Google, or GitHub. Not only is this way of authenticating more secure, but there is less effort required by the user. With only a few clicks, they can sign in to your web app. Such authentication is done using the OAuth protocol. It's a powerful and very commonly used protocol that allows users to authenticate with third-party applications using their existing login credentials. These days, it has become an essential part of modern web applications. In this blog post, we will explore how to implement OAuth authentication in a Next.js application. Why OAuth? Implementing authentication using OAuth is useful for a number of reasons. First of all, it allows users to sign in to your web app using their existing credentials from a trusted provider, such as Facebook and Google. This eliminates the need to go through a tedious registration process, and most importantly, it eliminates the need to come up with a password for the web app. This has many benefits for both the web app owner and the user. Neither need to store the password anywhere, as the password is handled by the trusted OAuth provider. This means that even if the web app gets hacked for some reason, the attacker will not gain access to the user password. For exactly that reason, you'll often hear experienced developers advise to "never roll your own authentication". OAuth in Next.js Next.js is the most popular React metaframework, and this gives you plenty of options and libraries for implementing authentication in your app. The most popular one, by far, is definitely Auth.js, formerly named NextAuth.js. With this library, you can get OAuth running in only a few simple steps, as we'll show in this blog post. We'll show you how to utilize the latest features in both Next.js and Auth.js to set up an OAuth integration using Facebook. Implementing OAuth in Next.js 13 Using Auth.js Creating Facebook App Before starting with the project, let's create an app on Facebook. This is a prerequisite to using Facebook as an OAuth provider. To do this, you'll need to go to Meta for Developers and create an account there by clicking on "Get Started". Once this is done, you can view the apps dashboard and click "Create App" to create your app. Since this is an app that will be used solely for Facebook login, we can choose "Consumer" as the type of the app, and you can pick any name for it. In our case, we used "Next.js OAuth Demo". After the app is created, it's immediately visible on the dashboard. Click the app, and then click Settings / Basic on the left menu to show both the app ID and the app secret - this will be used by our Next.js app. Setting Up Next.js For the purpose of this blog post, we'll create a new Next.js project from scratch so you can have a good reference project with minimum features and dependencies. In the shell, execute the npx create-next-app@latest --typescript` command and follow the prompts: `shell ✔ What is your project named? … nextjs-with-facebook-oauth ✔ Would you like to use ESLint with this project? … Yes ✔ Would you like to use src/` directory with this project? … No ✔ Would you like to use experimental app/` directory with this project? … Yes ✔ What import alias would you like configured? … @/ Creating a new Next.js app in /Users/dario/Projects/nextjs-with-facebook-oauth. ` As you can see, we've also used this opportunity to play with the experimental app` directory which is still in beta, but is the way Next.js apps will be built in the future. In our project, we've also set up Tailwind just to design the login page more quickly. Next, install the Auth.js library: `shell npm install @auth/core next-auth ` Now we need to create a catch-all API route under /api/auth` that will be handled by the Auth.js library. We'll do this by creating the following file: `typescript // pages/api/auth/[...nextauth].ts import NextAuth, { NextAuthOptions } from "next-auth"; import FacebookProvider from "next-auth/providers/facebook"; export const authOptions: NextAuthOptions = { providers: [ FacebookProvider({ clientId: process.env.FACEBOOKAPP_ID as string, clientSecret: process.env.FACEBOOKAPP_SECRET as string, }), ], }; export default NextAuth(authOptions); ` Note that even though we will be utilizing Next.js 13's app` directory, we need to place this route in the `pages` directory, as Auth.js doesn't yet support placing its API handler in the `app` directory. This is the only case where we will be using `pages`, though. In your project root, create a .env.local` file with the following contents: ` FACEBOOKAPP_ID=[app ID from the Facebook apps dashboard] FACEBOOKAPP_SECRET=[app secret from the Facebook apps dashboard] NEXTAUTHSECRET=[generate this value by going to https://generate-secret.vercel.app/32] NEXTAUTHURL=http://localhost:3000 ` All the above environment variables except NEXTAUTH_URL` are considered secret, and you should avoid committing them in the repository. Now, moving on to the React components, we'll need to have a few components that will perform the following functionality: - Display the sign-in button if the user is not authenticated - Otherwise, display user name and a sign-out button The Home` component that was auto-generated by Next.js is a server component and we can use `getServerSession()` from Auth.js to get the user's session. Based on that, we'll show either the sign-in component or the logged-in user information. `authOptions` provided to getServerSession() is the object that is defined in the API route. `typescript // /app/page.tsx import "./globals.css"; import { getServerSession } from "next-auth/next"; import { authOptions } from "pages/api/auth/[...nextauth]"; import { UserInformation } from "@/app/UserInformation"; import { SignIn } from "@/app/SignIn"; export default async function Home() { const session = await getServerSession(authOptions); return ( {session?.user?.name ? ( ) : ( )} ); } ` The SignIn` component has the sign-in button. The sign-in button needs to open an URL on Facebook that will initiate the authentication process. Once the authentication process is completed, it will invoke a "callback"- a special URL on the app side that is handled by Auth.js. `typescript // app/SignIn.tsx const loginUrl = https://www.facebook.com/v16.0/dialog/oauth?client_id=${ process.env.FACEBOOKAPP_ID }&redirecturi=${encodeURI( ${process.env.NEXTAUTH_URL}/api/auth/callback/facebook` )}; export function SignIn() { return ( Continue with Facebook ); } ` The UserInformation`, on the other hand, is displayed after the authentication process is completed. Unlike other components, this needs to be a client component to utilize the *signOut* method from Auth.js, which only works client-side. `typescript // app/UserInformation.tsx "use client"; import { signOut } from "next-auth/react"; export interface UserInformationProps { username: string; } export function UserInformation({ username }: UserInformationProps) { return ( You are logged in as {username}.{" "} signOut({ redirect: true })}> Sign out . ); } ` And that's it! Now run the project using npm run dev` and you should be able to authenticate to Facebook as shown below: Conclusion In conclusion, implementing OAuth-based authentication in Next.js is relatively straightforward thanks to Auth.js. This library not only comes with built-in Facebook support, but it also comes with 60+ other popular services, such as Google, Auth0, and more. We hope this blog post was useful, and you can always refer to the CodeSandbox project if you want to view the full source code. For other Next.js demo projects, be sure to check out starter.dev, where we already have a Next.js starter kit that can give you the best-practices in integrating Next.js with other libraries....

Intro to DevRel: What's the Difference Between External and Internal DevRel Programs? cover image

Intro to DevRel: What's the Difference Between External and Internal DevRel Programs?

Developer Relations (DevRel__) is a proactive, multifaceted discipline that bridges the gap between developers and companies to drive adoption while cultivating an energetic and supportive developer community for their product, service, or technology. The term and the profession are often misunderstood even among those in other technical roles. Some have never heard of DevRel before, and others believe it’s a kind of tech support for developers. Many organizations even think starting a DevRel program means just giving away free software and hoping it catches on. But DevRel is none of these things. At its core, a successful DevRel program builds strong bonds within their target market to ensure that developers can interface with a company or organization behind the product they’re using. Great teams establish authentic connections with developers, cultivate trust, and actively engage with them. DevRel defies traditional marketing strategies. Instead of prioritizing numbers and eyes that contribute to a sales funnel, it focuses on enhancing developer satisfaction. This creates a feedback loop between users and a company to better meet their needs, and foster a sense of collaboration within a product’s user community. The Two Main Domains of Developer Relations DevRel is split into 2 main domains__: __external__ and __internal__. External: Accessing an existing developer community If a company already has a product with an existing community or a product that may appeal to an existing community, and they want to establish a DevRel program around it, this would fall within the external domain. A successful external program will establish credibility and support developers through a number of evangelistic measures like blog posts, tutorials, webinars, giving talks at meetups and conferences, or creating useful code examples to teach concepts. These activities and their goals are rarely product-specific. Instead, they incorporate a number of technologies within their product’s technical ecosystem to demonstrate its value to a developer’s workflow. I had the pleasure of working with Doron Sherman during his tenure at Cloudinary as VP of Developer Relations. Doron has extensive experience with building developer communities, and successfully advocated internally to build a website called Media Jams, a learning resource for developers working with media in their apps. By having this initiative live under Developer Relations, and not under Product, Marketing, or Engineering, Doron and his team built quickly and created a site that prioritized education, without needing to meet the business objectives of other parts of the organization. > “Media Jams has had great organic growth as a community resource. We were able to attract non-Cloudinary users as well as organic search traffic of those looking for media use cases who would have otherwise gotten lost in the Cloudinary docs and/or could not find help through the Cloudinary blog or knowledge base.” says Doron. Internal: Building a developer community In order to support the adoption and retention of developers using a product, companies must have a space where developers can interface with them. Building their own community around a product is the best way to do this. By creating open lines of communication, developers can provide immediate feedback about a product in a productive way to product and engineering teams thereby shortening the feedback loop and improving the speed at which a team is able to innovate based on user needs. This strategy falls under the internal domain. These forums also provide synergistic opportunities for developers that are using a product to learn from each other. By working on similar problems, developers are able to bond and feel more ownership or excitement toward a product, increasing user retention. Danny Thompson, a developer influencer and mentor who has built a community of over a quarter million followers, says that he admires Appwrite’s DevRel program, helmed by Tessa Mero, Head of Developer Relations: > “The Appwrite DevRel team is great at answering questions. They are on Discord, jumping on calls with developers, answering questions, and doing office hours, all of which are super valuable in building that community. The main difference between Appwrite DevRel and other teams is, a lot of communities are run very passively and not always available or taking an active approach within community forums to help out.” - Danny Thompson on Appwrite. > “When we think about how to become successful as a company through DevRel, our first consideration is, what made us successful in the first place? Appwrite became an open-source company and a successful open-source project because of community, so we focus on a community-first approach. Contributors and developers that have supported us since before we were a company are what led us to where we are now. Every initiative, every planning, and everything we do on our team, we consider the community's feedback and perspective before we make any decisions.” - Tessa Mero at Appwrite. The Value-First Approach to Developer Relations Successful DevRel programs prioritize delivering value to cultivate credibility among developers and support product adoption free from reciprocal demands. External efforts involve engaging with existing technology communities, establishing credibility through various evangelistic measures, and delivering value to the community. On the other hand, internal programs build communities around their product, facilitating direct communication between developers and the company. These internal forums not only enhance user retention but also foster a space for developers to learn from each other, creating a sense of ownership and excitement around the product. And by diverting equity to these two programs, DevRel teams find new users, retain them, and receive invaluable feedback. Real-world examples, such as Doron Sherman's work at Cloudinary and Tessa Mero's leadership at Appwrite, showcase the effectiveness of DevRel in action, and highlight how DevRel programs contribute to the success and sustainability of developer-focused products. In the ever-evolving landscape of technology, DevRel emerges not only as a bridge between developers and organizations, but as a crucial driver of innovation, ensuring products remain relevant, adaptive, and deeply integrated into the communities they serve. If you’re thinking about building a successful DevRel program for the first time, the best place to start is to reflect on some of your favorite brands and how they connect with the developer community. Do they simply distribute discount codes and free swag, or are they reaching out to their users, and providing them a platform to learn, collaborate with others, and contribute? If they are, what methods do they use, and how do those methods coincide with your team’s existing strengths? And if you ever have any questions or want to connect with a DevRel specialist, do not hesitate to reach out!...