> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploying a Remix JS app on Upsun in 5 minutes

> Learn how to deploy a Remix JS application on Upsun in just 5 minutes. This tutorial covers creating a project, setting up the Node.js server, and deploying your app.


export const PostMeta = ({data = {}}) => {
  const {author, date, image} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (!image && authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
      {image && <img src={image} alt="" className="post-meta-image" aria-hidden="true" />}
    </div>;
};

<PostMeta data={{ author: ["gmoigneu"], date: "2024-03-01T02:00:00+00:00" }} />

Deploying any JavaScript/Node based framework on Upsun is easy. But as Remix does not have any bundled web server, let's see how we can run it.

We will implement and deploy the [Remix Tutorial](https://remix.run/docs/en/main/start/tutorial) here so you can refer to it if you need to grab the actual code.

## Initializing the Remix application

Let's start by initializing our application:

```bash {filename="Terminal"} theme={null}
npx create-remix@latest --template remix-run/remix/templates/remix-tutorial remix
Need to install the following packages:
create-remix@2.8.0
Ok to proceed? (y)

 remix   v2.8.0 💿 Let's build a better website...
      ◼  Directory: Using remix as project directory

      ◼  Template: Using remix-run/remix/templates/remix-tutorial...
      ✔  Template copied

   git   Initialize a new git repository?
         Yes

  deps   Install dependencies with npm?
         Yes

      ✔  Dependencies installed

      ✔  Git initialized

  done   That's it!

         Enter your project directory using cd ./remix
         Check out README.md for development and deploy instructions.
```

We can start it locally with `npm run dev`:

```bash {filename="Terminal"} theme={null}
npm run dev

> dev
> remix dev --manual

 💿  remix dev

 info  building...
 info  built (234ms)
[remix-serve] http://localhost:3000 (http://10.1.10.29:3000)
```

We have followed the amazing tutorial and we are now presented with a fully working application:

<img src="https://mintcdn.com/upsun-c9761871/OR9CPqO13fyi9LoK/images/posts/tutorials/deploying-remixjs-app-on-upsun/1_x2OZxpOUL42rVP5IzPJjtA.gif?s=3617934c8576d09d549338f7b0518b48" alt="Remix Application" width="2430" height="1364" data-path="images/posts/tutorials/deploying-remixjs-app-on-upsun/1_x2OZxpOUL42rVP5IzPJjtA.gif" />

Don't forget to commit your changes to the local repository.

## Creating the Upsun configuration

Let's move to the fun part. Make sure your Upsun CLI is installed and logged in with `upsun login`.

We now need to create a new project to deploy our Remix. We will use `upsun project:create`:

```bash {filename="Terminal"} theme={null}
upsun project:create

Selected organization: Nls (nls) (by default)
Creating a project under the organization Nls (nls)

* Project title (--title)
Default: Untitled Project
> Remix Deploy

* Region (--region)
The region where the project will be hosted.
Get a 3% discount on resources for regions with a carbon intensity of less than 100 gCO2eq/kWh.
  [au.platform.sh  ] Sydney, Australia (AWS)  [545 gC02eq/kWh]
  [au-2.platform.sh] Sydney, Australia (AZURE)  [545 gC02eq/kWh]
  [ca-1.platform.sh] Montreal, Canada (AWS)  [31 gC02eq/kWh]
  [ch-1.platform.sh] Zurich, Switzerland (GCP)  [91 gC02eq/kWh]
  [de-2.platform.sh] Frankfurt, Germany (GCP)  [416 gC02eq/kWh]
  [eu.platform.sh  ] Dublin, Ireland (AWS)  [386 gC02eq/kWh]
  [eu-2.platform.sh] Dublin, Ireland (AWS)  [386 gC02eq/kWh]
  [eu-4.platform.sh] Dublin, Ireland (AWS)  [386 gC02eq/kWh]
  [eu-5.platform.sh] Stockholm, Sweden (AWS)  [23 gC02eq/kWh]
  [fr-3.platform.sh] Gravelines, France (OVH)  [59 gC02eq/kWh]
  [fr-4.platform.sh] Paris, France (AZURE)  [59 gC02eq/kWh]
  [uk-1.platform.sh] London, United Kingdom (GCP)  [200 gC02eq/kWh]
  [us.platform.sh  ] Washington, United States (AWS)  [396 gC02eq/kWh]
  [us-2.platform.sh] Washington, United States (AWS)  [396 gC02eq/kWh]
  [us-3.platform.sh] Moses Lake, United States (AZURE)  [56 gC02eq/kWh]
  [us-4.platform.sh] Charleston, United States (GCP)  [647 gC02eq/kWh]
> us-3.platform.sh

Default branch (--default-branch)
The default Git branch name for the project (the production environment)
Default: main
>

Git repository detected: /Users/nls/psh/remix

Set the new project Remix Deploy as the remote for this repository? [Y/n]
```

Choose a name and a deployment region. The command will automatically set a new git remote on your local repository.

We now have an empty Upsun project ready to be deployed to:

```bash {filename="Terminal"} theme={null}
The Upsun Bot is activating your project

      ▄     ▄
      ▄█▄▄▄█▄
    ▄██▄███▄██▄
    █ █▀▀▀▀▀█ █
       ▀▀ ▀▀

The project is now ready!
gd33gxlviuaa4

  Region: us-3.platform.sh
  Project ID: gd33gxlviuaa4
  Project title: Remix Deploy
  URL: https://console.upsun.com/01hd1s2bjt44369c0923a70g44/gd33gxlviuaa4
  Git URL: gd33gxlviuaa4@git.us-3.platform.sh:gd33gxlviuaa4.git

Setting the remote project for this repository to: Remix Deploy (gd33gxlviuaa4)
```

We now need a configuration to tell Upsun how to run our project. Fortunately Upsun comes with the `upsun ify` command that will generate most of the YAML configuration automatically.

```text {filename="Terminal"} theme={null}
upsun ify
Welcome to Upsun!
Let's get started with a few questions.

We need to know a bit more about your project. This will only take a minute!

What language is your project using? We support the following: [JavaScript/Node.js]

✓ Detected dependency managers: Npm
Tell us your project's application name: [remix]

                       (\_/)
We're almost done...  =(^.^)=

Last but not least, unless you're creating a static website, your project uses services. Let's define them:

Select all the services you are using: []

You have not selected any service, would you like to proceed anyway? [Yes]
```

We haven't selected any services here because our data is fully local. If your application need to write some local files, look for the `mounts:` key in the newly created `.upsun/config.yaml`.

## Setting up the Node.js server

As mentionned in the Remix documentation, the default setup does not come with a web server capable of handling incoming requests.

> Remix is not an HTTP server, but rather a handler inside an existing HTTP server. Adapters allow the Remix handler to run inside the HTTP server. Some JavaScript runtimes, especially Node.js, have multiple ways to create an HTTP server. For example, in Node.js you can use Express.js, fastify, or raw http.createServer.

We will go the easy route and use the Remix App Server. It is a basic production-ready node.js Express server made by the Remix team. Good enough for our use-case.

Running it locally is straight-forward:

```bash {filename="Terminal"} theme={null}
npm run build
npx remix-serve build/index.js

[remix-serve] http://localhost:3000
GET / 200 - - 544.353 ms
```

Let's implement this in our Upsun configuration. As the generated configuration already includes the build command, we just need to specify our start command. Upsun automatically inject the `$HOST` environment variable so you don't need to explicitely add it to the command.

```yaml theme={null}
    web:
      # Commands are run once after deployment to start the application process.
      # More information: https://docs.upsun.com/create-apps/app-reference.html#web-commands
      commands:
        # The command to launch your app. If it terminates, it's restarted immediately.
        # You can use the $PORT or the $SOCKET environment variable depending on the socket family of your upstream
        start: "npx remix-serve build/index.js"
```

Commit your changes and push your code:

```bash {filename="Terminal"} theme={null}
git add .upsun
git commit -m "Add Upsun configuration"
upsun push
```

## Deploying to Upsun

The Upsun platform is now fetching the dependencies and building the project. After a couple minutes, our application is now deployed:

```bash {filename="Terminal"} theme={null}
Creating environment main
        Starting environment
        Updating endpoints for remix
        Opening application remix and its relationships
        Executing deploy hook for application remix

        Opening environment
        Environment configuration
          remix (type: nodejs:20, cpu: 0.5, memory: 224, disk: 512)

        Environment routes
          http://main-bvxea6i-gd33gxlviuaa4.us-3.platformsh.site/ redirects to https://main-bvxea6i-gd33gxlviuaa4.us-3.platformsh.site/
          http://www.main-bvxea6i-gd33gxlviuaa4.us-3.platformsh.site/ redirects to https://www.main-bvxea6i-gd33gxlviuaa4.us-3.platformsh.site/
          https://main-bvxea6i-gd33gxlviuaa4.us-3.platformsh.site/ is served by application `remix`
          https://www.main-bvxea6i-gd33gxlviuaa4.us-3.platformsh.site/ redirects to https://main-bvxea6i-gd33gxlviuaa4.us-3.platformsh.site/

      Blackfire build scheduled
```

You can access the app on the auto-generated URL:

<img src="https://mintcdn.com/upsun-c9761871/OR9CPqO13fyi9LoK/images/posts/tutorials/deploying-remixjs-app-on-upsun/1_m6HJ6m1JzmAwoDyI9TJ9Hw.webp?fit=max&auto=format&n=OR9CPqO13fyi9LoK&q=85&s=2252635fb3629c029b95ef48bc6426a5" alt="Remix Application Deployed" width="1400" height="903" data-path="images/posts/tutorials/deploying-remixjs-app-on-upsun/1_m6HJ6m1JzmAwoDyI9TJ9Hw.webp" />

Congratulations! Our app is deployed and working. If you want to add more customization to how requests are handled, the Remix documentation explains how to implement `@remix-run/express`.
