Skip to content

Choosing Remix: The JavaScript Fullstack Framework and Building Your First Application

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Remix is a React framework for building server-side rendering full-stack applications. It focuses on using web standards to build modern user interfaces. Remix was created by the team behind React Router, and was recently open-sourced for the community.

Remix extends most of the core functionality of React and React Router with server-side rendering, server requests, and backend optimization.

But why start using Remix? Remix comes with a number of advantages:

  • Nested routes - nested routes help eliminate nearly every loading state by using React Router’s outlet to render nested routes from directories and subdirectories.

  • Setup is easy! Spinning up a remix project takes just a few minutes and gets you productive immediately.

  • Remix uses server side progressive enhancement which means only necessary JavaScript, JSON, and CSS content is sent to the browser.

  • Remix focuses on server-side rendering.

  • File-system-based routing automatically generates the router configuration based on your file directory.

  • Remix is built on React which makes it easy to use if you know React.

Key Features

Let’s highlight a few key features of Remix you should be aware of.

Nested Route

Routes in Remix are file-system-based. Any component in the route directory is handled as a route and rendered to its parent outlet components.

If you define a parent component inside the routes directory, and then different routes inside a directory with the same name as the parent component, the latter will be nested within the first one.

Error boundaries

Error handling with applications is critical. In many cases, a single error can cause the entire application to be affected.

With Remix, when you get an error in a Remix component or a nested route, the errors are limited to the component and the component will fail to render or it will display the error without disrupting the entire application’s functionality.

Loading State

Remix handles loading states in parallel on the server and sends the fully formed HTML document to the browser; this eliminates the need to use a loading skeleton or spinner when fetching data or submitting form data.

Loaders and Actions

Among the most exciting features of Remix are Loaders and Actions.

These are special functions:

Loaders are functions (Hooks) that retrieve dynamic data from the database or API using the native fetch API. You can add loaders to a component by calling the useLoaderData() hook.

Actions are functions used to mutate data. Actions are mostly used to send form data to the API or database, to make changes to an API, or perform a delete action.

Building Your First Remix App

The next portion of this blog post will show you how to build your first Remix app!

We will be building a small blog app with primsa’s SQLite to store the posts.

To start a Remix project the prerequisites are:

  • A familiarity with JavaScript and React
  • Node.js installed
  • A code editor i.e VSCode

Open your system terminal and run:

npx create-remix@latest

You can accept the default prompts or use your own preferences.

Screenshot 2022-09-16 1.14.53 PM

Remix will install all the dependencies it requires and scaffold the application with directories and files.

In the project’s directory let install the other dependencies we will be using, run:

npm install bootstrap

You should use something like the directory in the image.

Screenshot 2022-09-16 1.17.44 PM

File structure walk through

The app directory contains the main building files for our Remix application.

The route directory holds all the routes that expose the exported default function as the route handler from the file.

entry.client.jsx and entry.server.jsx are core Remix’s files. Remix uses entry.client.jsx as the entry point for the browser bundle, and uses entry.server.jsx to generate the HTTP response when rendering on the server.

root.jsx is the root component of Remix application, the default export is the layout component that renders the rest of the app in an <Outlet />

These are the files we really want to touch on in our project. To learn more about the file directory, check out Remix’s API conventions.

Project set up

Open the root.jsx file and replace the code with:

import { Links, LiveReload, Meta, Outlet } from "@remix-run/react";
import bootstrapCSS from "bootstrap/dist/css/bootstrap.min.css";

export const links = () => [{ rel: "stylesheet", href: bootstrapCSS }];

export const meta = () => ({
  charset: "utf-8",
  viewport: "width=device-width,initial-scale=1",
});

export default function App() {
  return (
    <Document>
      <Layout>
        <Outlet />
      </Layout>
    </Document>
  );
}

function Layout({ children }) {
  return (
    <>
      <nav className="navbar navbar-expand-lg navbar-light bg-light px-5 py-3">
        <a href="/" className="navbar-brand">
          Remix
        </a>
        <ul className="navbar-nav mr-auto">
          <li className="nav-item">
            <a className="nav-link" href="/posts">Posts </a>
          </li>
        </ul>
      </nav>

      <div className="container">{children}</div>
    </>
  );
}

function Document({ children }) {
  return (
    <html>
      <head>
        <Links />
        <Meta />
        <title>ThisDot Labs Code Blog</title>
      </head>
      <body>
        {children}
        {process.env.NODE_ENV === "development" ? <LiveReload /> : null}
      </body>
    </html>
  );
}

Since we are using bootstrap for styling we imported the minified library, Remix uses the <link rel="stylesheet"> to add stylesheet at component level using the Route Module links export.

The links function defines which elements to add to the page when the user visits the route. Visit Remix Styles to learn more about stylesheets in Remix.

Similar to the stylesheet, Remix can add the Meta tags to the route header by calling Meta Module. The meta function defines which meta elements you can add to the routes. This is a huge plus for your application’s SEO.

The app file has three components with a default export of the App component. Here, we declared a Document Component for the HTML document template, a Layout Component to further improve the template layout for rendering components, and added the {process.env.NODE_ENV === "development" ? <LiveReload /> : null} for Hot reload when changing things in the file during development.

One important note is to not confuse Links with Link. The latter, Link, is a router component for matching routes in Remix’s apps. We will handle the components to match our routes below.

To test out the application:

npm run dev

You should have a similar app as shown below:

Screenshot 2022-09-16 1.20.17 PM

Let’s configure the db we will use for the app. We will use primsa SQLite to store the posts.

Install the Prisma packages in your project:

npm install prisma @prisma-client

Now let’s initialize the prisma SQlite in project:

npx prisma init --datasource-provider sqlite

This will add a Prisma directory to the project.

Open prisma/schema.prisma and add the following code at the end of the file:

model Post {
  slug     String @id
  title    String
  body String

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

This is the schema for the blog database.

Simply, the schema is identifying that we will need a slug which will be a unique string and used for our blog’s unique route. The title property is also a string type for the title of our blog. The body type of string will hold the body, and finally we want to have the dates created and updated.

Now, let’s push the schema to Prisma:

npx prisma db push

Prisma will create a dev.db in the Prisma directory.

Now let's seed our database with a couple posts. Create prisma/seed.js and add this to the file.

const {PrismaClient} = require('@prisma/client')

const prisma = new PrismaClient()

async function seed() {

  await Promise.all(
    getPosts().map(post => {

      return prisma.post.create({data: post})
    })
  )
}

seed()

function getPosts() {
  return [
    {
      title: 'JavaScript Performance Tips',
      slug: "javaScript-performance-tips",
      body: `We will look at 10 simple tips and tricks to increase the speed of your code when writing JS`,
    },
    {
      title: 'Tailwind vs. Bootstrap',
      slug: `tailwind-vs-bootstrap`,
      body: `Both Tailwind and Bootstrap are very popular CSS frameworks. In this article, we will compare them`,
    },
    {
      title: 'Writing Great Unit Tests',
      slug: `writing-great-unit-tests`,
      body: `We will look at 10 simple tips and tricks on writing unit tests in JavaScript`,
    },
    {
      title: 'What Is New In PHP 8?',
      slug: `what-is-new-in-PHP-8`,
      body: `In this article we will look at some of the new features offered in version 8 of PHP`,
    },
  ]
}

Now edit the package.json file just before the script property and add this:

  "prisma": {
    "seed": "node prisma/seed"
  },
  "scripts": {
… 

Now, to seed the db with Prisma, run:

npx prisma db seed

Our database setup is now done.

There is just one more thing for database connection, We need to create a typescript file app/utils/db.server.ts.

We specify a server file by appending .server at the end of the file name, Remix will compile and deploy this file on the server..

Add the code below to the file:

import { PrismaClient } from "@prisma/client";

let db: PrismaClient

declare global {
  var __db: PrismaClient | undefined
}

if (process.env.NODE_ENV === 'production') {
  db = new PrismaClient()
  db.$connect()
} else {
  if (!global.__db) {
    global.__db = new PrismaClient()
    global.__db.$connect()
  }

  db = global.__db
}

export { db }

Now, go back to the code editor and create post.jsx in the route directory, and add the following code:

import { Outlet } from "@remix-run/react"

function Posts() {
  return (
    <>
      <Outlet />
    </>
  )
}

export default Posts

All we are doing is rendering our nested routes of posts here, nothing crazy.

Now create routes/posts/index.jsx

import { Link, useLoaderData } from "@remix-run/react";
import {db} from '../../utils/db.server';

export const loader = async () => {
  const data = {
    posts: await db.post.findMany({
      take: 20,
      select: {slug: true, title: true, createdAt: true},
      orderBy: {createdAt: 'desc'}
    })
  }
  return data
}

function PostItems() {
  const {posts} = useLoaderData()
  return (
    <>
      <div>
        <h1>Posts</h1>
        <Link to='/posts/new' className="btn btn-primary">New Post</Link>
      </div>
      <div className="row mt-3">
      {posts.map(post => (
        <div className="card mb-3 p-3" key={post.id}>
          <Link to={`./${post.slug}`}>
            <h3 className="card-header">{post.title}</h3>
            {new Date(post.createdAt).toLocaleString()}
          </Link>
        </div>
      ))}

      </div>
    </>
  )
}

export default PostItems

Here, we are declaring the loader and fetching all the posts from the database with a limit of 20 and destructuring the posts from the useLoaderData hook, then finally looping through the returned posts.

Dynamic Routes

Remix dynamic routes are defined with a $ sign and the id key. We will want to use the slugs properties as our dynamic routes for the blogs.

To do this, create app/routes/posts/$postSlug.jsx

import {Link, useLoaderData} from "@remix-run/react";
import {db} from '../../utils/db.server';

export const loader = async ({params}) => {
  const post = await db.post.findUnique({
    where: {slug: params.postSlug}
  })

  if (!post) throw new Error('Post not found')

  const data = {post}

  return data
}

function Post() {
  const {post} = useLoaderData() 
  return (
    <div className="card w-100">
      <div className="card-header">
        <h1>{post.title}</h1>
      </div>
      <div className="card-body">{post.body}</div>
      <div className="card-footer">
        <Link to='/posts' className="btn btn-danger">Back</Link>
      </div>
    </div>
  )
}

export default Post

Here, the loader is fetching from the database using the unique slug we provided and rendering the article.

To add where to post new blogs, create app/routes/posts/new.jsx, and the following code:

import { redirect } from "@remix-run/node";
import { Link } from "@remix-run/react";
import { db } from "../../utils/db.server";

export const action = async ({ request }) => {
  const form = await request.formData();

  const title = form.get("title");
  const body = form.get("body");
  const fields = { title, body, slug: title.split(‘ ‘).join(‘-’) };

  const post = await db.post.create({ data: fields });

  return redirect(`/posts/${post.slug}`);
};

function NewPost() {
  return (
    <div className="card w-100">
<form method="POST">
      <div className="card-header">
        <h1>New Post</h1>
        <Link to="/posts" className="btn btn-danger">
          Back
        </Link>
      </div>

      <div className="card-body">

          <div className="form-control my-2">
            <label htmlFor="title">Title</label>
            <input type="text" name="title" id="title" />
          </div>
          <div className="form-control my-2">
            <label htmlFor="body">Post Body</label>
            <textarea name="body" id="body" />
          </div>

      </div>
      <div className="card-footer">
        <button type="submit" className="btn btn-success">
          Add Post
        </button>
      </div>
        </form>
    </div>
  );
}

export default NewPost;

We created an action function to handle the form post request, and Remix will catch all the formData by passing the request.

And there you have it! You’ve created a functional blog application.

Conclusion

In this article, we learned why Remix is a great choice for building your next application, and why it’s becoming a more popular framework to use amongst developers.

We also explored the core features of Remix and learned how to build a sample blog application using a database.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

The Importance of a Scientific Mindset in Software Engineering: Part 2 (Debugging) cover image

The Importance of a Scientific Mindset in Software Engineering: Part 2 (Debugging)

The Importance of a Scientific Mindset in Software Engineering: Part 2 (Debugging) In the first part of my series on the importance of a scientific mindset in software engineering, we explored how the principles of the scientific method can help us evaluate sources and make informed decisions. Now, we will focus on how these principles can help us tackle one of the most crucial and challenging tasks in software engineering: debugging. In software engineering, debugging is often viewed as an art - an intuitive skill honed through experience and trial and error. In a way, it is - the same as a GP, even a very evidence-based one, will likely diagnose most of their patients based on their experience and intuition and not research scientific literature every time; a software engineer will often rely on their experience and intuition to identify and fix common bugs. However, an internist faced with a complex case will likely not be able to rely on their intuition alone and must apply the scientific method to diagnose the patient. Similarly, a software engineer can benefit from using the scientific method to identify and fix the problem when faced with a complex bug. From that perspective, treating engineering challenges like scientific inquiries can transform the way we tackle problems. Rather than resorting to guesswork or gut feelings, we can apply the principles of the scientific method—forming hypotheses, designing controlled experiments, gathering and evaluating evidence—to identify and eliminate bugs systematically. This approach, sometimes referred to as "scientific debugging," reframes debugging from a haphazard process into a structured, disciplined practice. It encourages us to be skeptical, methodical, and transparent in our reasoning. For instance, as Andreas Zeller notes in the book _Why Programs Fail_, the key aspect of scientific debugging is its explicitness: Using the scientific method, you make your assumptions and reasoning explicit, allowing you to understand your assumptions and often reveals hidden clues that can lead to the root cause of the problem on hand. Note: If you'd like to read an excerpt from the book, you can find it on Embedded.com. Scientific Debugging At its core, scientific debugging applies the principles of the scientific method to the process of finding and fixing software defects. Rather than attempting random fixes or relying on intuition, it encourages engineers to move systematically, guided by data, hypotheses, and controlled experimentation. By adopting debugging as a rigorous inquiry, we can reduce guesswork, speed up the resolution process, and ensure that our fixes are based on solid evidence. Just as a scientist begins with a well-defined research question, a software engineer starts by identifying the specific symptom or error condition. For instance, if our users report inconsistencies in the data they see across different parts of the application, our research question could be: _"Under what conditions does the application display outdated or incorrect user data?"_ From there, we can follow a structured debugging process that mirrors the scientific method: - 1. Observe and Define the Problem: First, we need to clearly state the bug's symptoms and the environment in which it occurs. We should isolate whether the issue is deterministic or intermittent and identify any known triggers if possible. Such a structured definition serves as the groundwork for further investigation. - 2. Formulate a Hypothesis: A hypothesis in debugging is a testable explanation for the observed behavior. For instance, you might hypothesize: _"The data inconsistency occurs because a caching layer is serving stale data when certain user profiles are updated."_ The key is that this explanation must be falsifiable; if experiments don't support the hypothesis, it must be refined or discarded. - 3. Collect Evidence and Data: Evidence often includes logs, system metrics, error messages, and runtime traces. Similar to reviewing primary sources in academic research, treat your raw debugging data as crucial evidence. Evaluating these data points can reveal patterns. In our example, such patterns could be whether the bug correlates with specific caching mechanisms, increased memory usage, or database query latency. During this step, it's essential to approach data critically, just as you would analyze the quality and credibility of sources in a research literature review. Don't forget that even logs can be misleading, incomplete, or even incorrect, so cross-referencing multiple sources is key. - 4. Design and Run Experiments: Design minimal, controlled tests to confirm or refute your hypothesis. In our example, you may try disabling or shortening the cache's time-to-live (TTL) to see if more recent data is displayed correctly. By manipulating one variable at a time - such as cache invalidation intervals - you gain clearer insights into causation. Tools such as profilers, debuggers, or specialized test harnesses can help isolate factors and gather precise measurements. - 5. Analyze Results and Refine Hypotheses: If the experiment's outcome doesn't align with your hypothesis, treat it as a stepping stone, not a dead end. Adjust your explanation, form a new hypothesis, or consider additional variables (for example, whether certain API calls bypass caching). Each iteration should bring you closer to a better understanding of the bug's root cause. Remember, the goal is not to prove an initial guess right but to arrive at a verifiable explanation. - 6. Implement and Verify the Fix: Once you're confident in the identified cause, you can implement the fix. Verification doesn't stop at deployment - re-test under the same conditions and, if possible, beyond them. By confirming the fix in a controlled manner, you ensure that the solution is backed by evidence rather than wishful thinking. - Personally, I consider implementing end-to-end tests (e.g., with Playwright) that reproduce the bug and verify the fix to be a crucial part of this step. This both ensures that the bug doesn't reappear in the future due to changes in the codebase and avoids possible imprecisions of manual testing. Now, we can explore these steps in more detail, highlighting how the scientific method can guide us through the debugging process. Establishing Clear Debugging Questions (Formulating a Hypothesis) A hypothesis is a proposed explanation for a phenomenon that can be tested through experimentation. In a debugging context, that phenomenon is the bug or issue you're trying to resolve. Having a clear, falsifiable statement that you can prove or disprove ensures that you stay focused on the real problem rather than jumping haphazardly between possible causes. A properly formulated hypothesis lets you design precise experiments to evaluate whether your explanation holds true. To formulate a hypothesis effectively, you can follow these steps: 1. Clearly Identify the Symptom(s) Before forming any hypothesis, pin down the specific issue users are experiencing. For instance: - "Users intermittently see outdated profile information after updating their accounts." - "Some newly created user profiles don't reflect changes in certain parts of the application." Having a well-defined problem statement keeps your hypothesis focused on the actual issue. Just like a research question in science, the clarity of your symptom definition directly influences the quality of your hypothesis. 2. Draft a Tentative Explanation Next, convert your symptom into a statement that describes a _possible root cause_, such as: - "Data inconsistency occurs because the caching layer isn't invalidating or refreshing user data properly when profiles are updated." - "Stale data is displayed because the cache timeout is too long under certain load conditions." This step makes your assumption about the root cause explicit. As with the scientific method, your hypothesis should be something you can test and either confirm or refute with data or experimentation. 3. Ensure Falsifiability A valid hypothesis must be falsifiable - meaning it can be proven _wrong_. You'll struggle to design meaningful experiments if a hypothesis is too vague or broad. For example: - Not Falsifiable: "Occasionally, the application just shows weird data." - Falsifiable: "Users see stale data when the cache is not invalidated within 30 seconds of profile updates." Making your hypothesis specific enough to fail a test will pave the way for more precise debugging. 4. Align with Available Evidence Match your hypothesis to what you already know - logs, stack traces, metrics, and user reports. For example: - If logs reveal that cache invalidation events aren't firing, form a hypothesis explaining why those events fail or never occur. - If metrics show that data served from the cache is older than the configured TTL, hypothesize about how or why the TTL is being ignored. If your current explanation contradicts existing data, refine your hypothesis until it fits. 5. Plan for Controlled Tests Once you have a testable hypothesis, figure out how you'll attempt to _disprove_ it. This might involve: - Reproducing the environment: Set up a staging/local system that closely mimics production. For instance with the same cache layer configurations. - Varying one condition at a time: For example, only adjust cache invalidation policies or TTLs and then observe how data freshness changes. - Monitoring metrics: In our example, such monitoring would involve tracking user profile updates, cache hits/misses, and response times. These metrics should lead to confirming or rejecting your explanation. These plans become your blueprint for experiments in further debugging stages. Collecting and Evaluating Evidence After formulating a clear, testable hypothesis, the next crucial step is to gather data that can either support or refute it. This mirrors how scientists collect observations in a literature review or initial experiments. 1. Identify "Primary Sources" (Logs, Stack Traces, Code History): - Logs and Stack Traces: These are your direct pieces of evidence - treat them like raw experimental data. For instance, look closely at timestamps, caching-related events (e.g., invalidation triggers), and any error messages related to stale reads. - Code History: Look for related changes in your source control, e.g. using Git bisect. In our example, we would look for changes to caching mechanisms or references to cache libraries in commits, which could pinpoint when the inconsistency was introduced. Sometimes, reverting a commit that altered cache settings helps confirm whether the bug originated there. 2. Corroborate with "Secondary Sources" (Documentation, Q&A Forums): - Documentation: Check official docs for known behavior or configuration details that might differ from your assumptions. - Community Knowledge: Similar issues reported on GitHub or StackOverflow may reveal known pitfalls in a library you're using. 3. Assess Data Quality and Relevance: - Look for Patterns: For instance, does stale data appear only after certain update frequencies or at specific times of day? - Check Environmental Factors: For instance, does the bug happen only with particular deployment setups, container configurations, or memory constraints? - Watch Out for Biases: Avoid seeking only the data that confirms your hypothesis. Look for contradictory logs or metrics that might point to other root causes. You keep your hypothesis grounded in real-world system behavior by treating logs, stack traces, and code history as primary data - akin to raw experimental results. This evidence-first approach reduces guesswork and guides more precise experiments. Designing and Running Experiments With a hypothesis in hand and evidence gathered, it's time to test it through controlled experiments - much like scientists isolate variables to verify or debunk an explanation. 1. Set Up a Reproducible Environment: - Testing Environments: Replicate production conditions as closely as possible. In our example, that would involve ensuring the same caching configuration, library versions, and relevant data sets are in place. - Version Control Branches: Use a dedicated branch to experiment with different settings or configuration, e.g., cache invalidation strategies. This streamlines reverting changes if needed. 2. Control Variables One at a Time: - For instance, if you suspect data inconsistency is tied to cache invalidation events, first adjust only the invalidation timeout and re-test. - Or, if concurrency could be a factor (e.g., multiple requests updating user data simultaneously), test different concurrency levels to see if stale data issues become more pronounced. 3. Measure and Record Outcomes: - Automated Tests: Tests provide a great way to formalize and verify your assumptions. For instance, you could develop tests that intentionally update user profiles and check if the displayed data matches the latest state. - Monitoring Tools: Monitor relevant metrics before, during, and after each experiment. In our example, we might want to track cache hit rates, TTL durations, and query times. - Repeat Trials: Consistency across multiple runs boosts confidence in your findings. 4. Validate Against a Baseline: - If baseline tests manifest normal behavior, but your experimental changes manifest the bug, you've isolated the variable causing the issue. E.g. if the baseline tests show that data is consistently fresh under normal caching conditions but your experimental changes cause stale data. - Conversely, if your change eliminates the buggy behavior, it supports your hypothesis - e.g. that the cache configuration was the root cause. Each experiment outcome is a data point supporting or contradicting your hypothesis. Over time, these data points guide you toward the true cause. Analyzing Results and Iterating In scientific debugging, an unexpected result isn't a failure - it's valuable feedback that brings you closer to the right explanation. 1. Compare Outcomes to the hypothesis. For instance: - Did user data stay consistent after you reduced the cache TTL or fixed invalidation logic? - Did logs show caching events firing as expected, or did they reveal unexpected errors? - Are there only partial improvements that suggest multiple overlapping issues? 2. Incorporate Unexpected Observations: - Sometimes, debugging uncovers side effects - e.g. performance bottlenecks exposed by more frequent cache invalidations. Note these for future work. - If your hypothesis is disproven, revise it. For example, the cache may only be part of the problem, and a separate load balancer setting also needs attention. 3. Avoid Confirmation Bias: - Don't dismiss contrary data. For instance, if you see evidence that updates are fresh in some modules but stale in others, you may have found a more nuanced root cause (e.g., partial cache invalidation). - Consider other credible explanations if your teammates propose them. Test those with the same rigor. 4. Decide If You Need More Data: - If results aren't conclusive, add deeper instrumentation or enable debug modes to capture more detailed logs. - For production-only issues, implement distributed tracing or sampling logs to diagnose real-world usage patterns. 5. Document Each Iteration: - Record the results of each experiment, including any unexpected findings or new hypotheses that arise. - Through iterative experimentation and analysis, each cycle refines your understanding. By letting evidence shape your hypothesis, you ensure that your final conclusion aligns with reality. Implementing and Verifying the Fix Once you've identified the likely culprit - say, a misconfigured or missing cache invalidation policy - the next step is to implement a fix and verify its resilience. 1. Implementing the Change: - Scoped Changes: Adjust just the component pinpointed in your experiments. Avoid large-scale refactoring that might introduce other issues. - Code Reviews: Peer reviews can catch overlooked logic gaps or confirm that your changes align with best practices. 2. Regression Testing: - Re-run the same experiments that initially exposed the issue. In our stale data example, confirm that the data remains fresh under various conditions. - Conduct broader tests - like integration or end-to-end tests - to ensure no new bugs are introduced. 3. Monitoring in Production: - Even with positive test results, real-world scenarios can differ. Monitor logs and metrics (e.g. cache hit rates, user error reports) closely post-deployment. - If the buggy behavior reappears, revisit your hypothesis or consider additional factors, such as unpredicted user behavior. 4. Benchmarking and Performance Checks (If Relevant): - When making changes that affect the frequency of certain processes - such as how often a cache is refreshed - be sure to measure the performance impact. Verify you meet any latency or resource usage requirements. - Keep an eye on the trade-offs: For instance, more frequent cache invalidations might solve stale data but could also raise system load. By systematically verifying your fix - similar to confirming experimental results in research - you ensure that you've addressed the true cause and maintained overall software stability. Documenting the Debugging Process Good science relies on transparency, and so does effective debugging. Thorough documentation guarantees your findings are reproducible and valuable to future team members. 1. Record Your Hypothesis and Experiments: - Keep a concise log of your main hypothesis, the tests you performed, and the outcomes. - A simple markdown file within the repo can capture critical insights without being cumbersome. 2. Highlight Key Evidence and Observations: - Note the logs or metrics that were most instrumental - e.g., seeing repeated stale cache hits 10 minutes after updates. - Document any edge cases discovered along the way. 3. List Follow-Up Actions or Potential Risks: - If you discover additional issues - like memory spikes from more frequent invalidation - note them for future sprints. - Identify parts of the code that might need deeper testing or refactoring to prevent similar issues. 4. Share with Your Team: - Publish your debugging report on an internal wiki or ticket system. A well-documented troubleshooting narrative helps educate other developers. - Encouraging open discussion of the debugging process fosters a culture of continuous learning and collaboration. By paralleling scientific publication practices in your documentation, you establish a knowledge base to guide future debugging efforts and accelerate collective problem-solving. Conclusion Debugging can be as much a rigorous, methodical exercise as an art shaped by intuition and experience. By adopting the principles of scientific inquiry - forming hypotheses, designing controlled experiments, gathering evidence, and transparently documenting your process - you make your debugging approach both systematic and repeatable. The explicitness and structure of scientific debugging offer several benefits: - Better Root-Cause Discovery: Structured, hypothesis-driven debugging sheds light on the _true_ underlying factors causing defects rather than simply masking symptoms. - Informed Decisions: Data and evidence lead the way, minimizing guesswork and reducing the chance of reintroducing similar issues. - Knowledge Sharing: As in scientific research, detailed documentation of methods and outcomes helps others learn from your process and fosters a collaborative culture. Ultimately, whether you are diagnosing an intermittent crash or chasing elusive performance bottlenecks, scientific debugging brings clarity and objectivity to your workflow. By aligning your debugging practices with the scientific method, you build confidence in your solutions and empower your team to tackle complex software challenges with precision and reliability. But most importantly, do not get discouraged by the number of rigorous steps outlined above or by the fact you won't always manage to follow them all religiously. Debugging is a complex and often frustrating process, and it's okay to rely on your intuition and experience when needed. Feel free to adapt the debugging process to your needs and constraints, and as long as you keep the scientific mindset at heart, you'll be on the right track....

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers cover image

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers

Before she was a software developer at freeCodeCamp, Jessica Wilkins was a classically trained clarinetist performing across the country. Her days were filled with rehearsals, concerts, and teaching, and she hadn’t considered a tech career until the world changed in 2020. > “When the pandemic hit, most of my gigs were canceled,” she says. “I suddenly had time on my hands and an idea for a site I wanted to build.” That site, a tribute to Black musicians in classical and jazz music, turned into much more than a personal project. It opened the door to a whole new career where her creative instincts and curiosity could thrive just as much as they had in music. Now at freeCodeCamp, Jessica maintains and develops the very JavaScript curriculum that has helped her and millions of developers around the world. We spoke with Jessica about her advice for JavaScript learners, why musicians make great developers, and how inclusive communities are helping more women thrive in tech. Jessica’s Top 3 JavaScript Skill Picks for 2025 If you ask Jessica what it takes to succeed as a JavaScript developer in 2025, she won’t point you straight to the newest library or trend. Instead, she lists three skills that sound simple, but take real time to build: > “Learning how to ask questions and research when you get stuck. Learning how to read error messages. And having a strong foundation in the fundamentals” She says those skills don’t come from shortcuts or shiny tools. They come from building. > “Start with small projects and keep building,” she says. “Books like You Don’t Know JS help you understand the theory, but experience comes from writing and shipping code. You learn a lot by doing.” And don’t forget the people around you. > “Meetups and conferences are amazing,” she adds. “You’ll pick up things faster, get feedback, and make friends who are learning alongside you.” Why So Many Musicians End Up in Tech A musical past like Jessica’s isn’t unheard of in the JavaScript industry. In fact, she’s noticed a surprising number of musicians making the leap into software. > “I think it’s because music and code have a lot in common,” she says. “They both require creativity, pattern recognition, problem-solving… and you can really get into flow when you’re deep in either one.” That crossover between artistry and logic feels like home to people who’ve lived in both worlds. What the Tech Community Is Getting Right Jessica has seen both the challenges and the wins when it comes to supporting women in tech. > “There’s still a lot of toxicity in some corners,” she says. “But the communities that are doing it right—like Women Who Code, Women in Tech, and Virtual Coffee—create safe, supportive spaces to grow and share experiences.” She believes those spaces aren’t just helpful, but they’re essential. > “Having a network makes a huge difference, especially early in your career.” What’s Next for Jessica Wilkins? With a catalog of published articles, open-source projects under her belt, and a growing audience of devs following her journey, Jessica is just getting started. She’s still writing. Still mentoring. Still building. And still proving that creativity doesn’t stop at the orchestra pit—it just finds a new stage. Follow Jessica Wilkins on X and Linkedin to keep up with her work in tech, her musical roots, and whatever she’s building next. Sticker illustration by Jacob Ashley....

How to Build Apps with Great Startup Performance Using Qwik cover image

How to Build Apps with Great Startup Performance Using Qwik

In this article, we will recap the JS Drops Qwik workshop with Misko Hevery. This workshop provided an overview on Qwik, its unique features and a look at some example components. We will also address some of the questions raised at the end of the workshop. If you want to learn more about Qwik with Misko Hevery, then please check out this presentation on This Dot Media’s YouTube Channel. Also don’t forget to subscribe to get the latest on all things web development. Table of Contents - What is Qwik? - How to create a Counter Component in Qwik - Unique features of Qwik - Directory Based Routing - Slots in Qwik - Very little JavaScript in production - Resumability with Qwik - Lazy load components by default - Questions asked during the workshop - Are all these functions generated at build time or are they generated at runtime? What's the server consideration here (if any) or are we able to put everything behind a CDN? - How do you access elements in Qwik? - Can you force a download of something out of view? - What is the choice to use $ on Qwik declarations? - Can you explain the interop story with web components and Qwik? Any parts of the Qwik magic that aren’t available to us if, say, our web components are too complex? - Is there an ideal use case for Qwik? - When to use useWatch$ instead of useClientEffect$? - Conclusion What is Qwik? Qwik is a web framework that builds fast web applications with consistent performance at scale regardless of size or complexity. To get started with Qwik, run the following command: ` The Qwik CLI will prompt options to scaffold a starter project on your local machine. To start the demo application, run npm start and navigate to http://127.0.0.1:5173/ in the browser. How to create a Counter Component in Qwik Create a sub-directory in the routes directory named counter and add an index.tsx file with the component definition below. ` Now navigate to http://127.0.0.1:5173/counter and you should see the counter component rendered on the page. Unique features of Qwik Directory Based Routing Qwik is a directory-based routing framework. When we initiated Qwik, it created a routes sub-directory in the src directory and added index and layout files for route matching. The index.tsx is the base route component and the layout.tsx is the component for handling the base page layout. The sub-directories in the route directory serve as the application’s structure for route matching with its index.tsx files as the route components. Every index.tsx file does a look up for the layout component. If it doesn’t exist in the same directory, then it moves up to the parent directory. ` Slots in Qwik Qwik uses slots as a way of connecting content from the parent component to the child projection. The parent component uses the q:slot attribute to identify the source of the projection and the element to identify the destination of the projection. To learn more about slots, please check out the Qwik documentation. Very little JavaScript in production In production, Qwik starts the application with no JavaScript at startup, which makes the startup performance really fast. To see this in action, open the browser’s dev tools, click on the Network tab, and on the Filter tab select JS. You will notice the Vite files for hot module reloading are currently the only JavaScript files served which will not be shipped to production. Go to the filter tab and check the invert checkbox then in the filter input type select Vite. Resumability with Qwik Qwik applications do not require hydration to resume an application on the client. To see this in action, click on the increment button and observe the browser’s dev tools network tab. You will notice Qwik is downloading only the required amount of JavaScript needed. The way Qwik attaches the event to the DOM and handles the state of components is that it serializes the attribute, which tells the browser where to download the event handler and its state. To learn more about serialization with Qwik, read through the Qwik documentation. By default, the code associated with the click event will not download until the user triggers that event. On this interaction, Qwik will only download the minimum code for the event handler to work. To learn more about Qwik events, please read through the [documentation] (https://qwik.builder.io/docs/components/events/#events)....

What does it actually look like to build software with AI today? Not in theory, but in practice. cover image

What does it actually look like to build software with AI today? Not in theory, but in practice.

What does it actually look like to build software with AI today? Not in theory, but in practice. At the Leadership Exchange, this was the question at the center of the Developer Panel, where leaders from across the industry unpacked what’s really changing inside engineering teams and what organizations need to do right now to keep up. The Developer Panel at the Leadership Exchange explored the cutting edge of AI in software engineering and examined what organizations should focus on today to prepare for the future. Moderated by Jeff Cross, Co-Founder & CEO at Nx, the panel featured Victor Savkin, Cofounder & CTO at Nx, Alex Sover, Vice President of Engineering at OpenAP, Brent Zucker, Senior Director of Engineering at Visa, and Jonathan Fontanez, AI Engineering Lead at This Dot Labs. Panelists shared insights into how AI is transforming the software development lifecycle and how teams can adopt tools effectively while preparing for organizational change. Panelists discussed emerging workflows, including CI-in-the-loop, agentic healing, and context engineering. They examined how validation, code reviews, and PRDs are evolving alongside AI capabilities and how teams are integrating external sources such as production traces to improve quality and reliability. The discussion also covered what the next generation of agentic tools might look like and how these capabilities will shape engineering practices in the near future. Adoption of AI comes with challenges. Teams often rely on plugins or extensions without foundational understanding, and individual contributors may fear displacement. Panelists emphasized that education, governance, and skill-building are essential for teams to manage AI agents effectively while maintaining quality. They also highlighted the need to standardize workflows and ensure organizational alignment to fully leverage AI capabilities. The conversation extended beyond technical challenges to organizational implications. Panelists discussed how teams can avoid issues like Conway’s Law, manage distributed teams effectively, and evolve engineering practices alongside AI adoption. Leadership and management strategies play a crucial role in ensuring that AI integration delivers meaningful outcomes while maintaining efficiency and alignment with business objectives. Key Takeaways - AI workflows require both technical and organizational preparation. - Education, governance, and skill development are essential for successful implementation. - Forward-looking teams are rethinking validation, CI pipelines, and context management to fully leverage agentic AI. The discussion highlighted that adopting AI at the cutting edge is not just about new tools - it is about rethinking processes, workflows, and organizational culture. Companies that embrace this holistic approach are most likely to succeed in leveraging AI to its full potential. Are you interested in more conversations like this? Message us for an invite to the next, or for a private discussion around these topics. Tracy can be reached at tlee@thisdot.co....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co