lauroguedes / laravel-astro-landing
Keep an Astro landing page in the same repository as your Laravel application, scaffold it with one command, run it alongside `composer run dev`, and deploy it on its own.
Package info
github.com/lauroguedes/laravel-astro-landing
pkg:composer/lauroguedes/laravel-astro-landing
Fund package maintenance!
Requires
- php: ^8.3
- illuminate/console: ^12.0|^13.0
- illuminate/contracts: ^12.0|^13.0
- illuminate/filesystem: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/process: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- laravel/prompts: ^0.3.5
Requires (Dev)
- larastan/larastan: ^3.4
- laravel/pint: ^1.22
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^4.0|^5.0
- pestphp/pest-plugin-laravel: ^4.0|^5.0
- pestphp/pest-plugin-type-coverage: ^4.0|^5.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- rector/rector: ^2.0
- symfony/yaml: ^7.2|^8.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Keep an Astro landing page in the same repository as your Laravel application, and deploy it somewhere else.
A product's marketing site and the product itself change for different reasons and belong on different servers: the landing is static, cheap to host on a CDN and edited by whoever writes the copy; the application needs PHP, a database and a deploy that runs migrations. Keeping them in one repository keeps them in sync (same pull requests, same issues, and a landing that links to the routes that actually exist), as long as nothing couples them.
This package is the decoupling, set up in one command:
landing:installcreates the Astro project inlanding/from an empty template, an official example, any template repository, or a free theme from the Astro themes catalog.composer run devstarts the landing next to the application (Laravel 13.16+).landing:workflowwrites a GitHub Actions workflow that builds and deploys the landing to Cloudflare Pages, Netlify, Vercel, GitHub Pages or your own server, and runs only when the landing changed.
Installation
composer require lauroguedes/laravel-astro-landing --dev php artisan landing:install
It is a development dependency: nothing in it runs on the server that serves your application. The landing is deployed by GitHub Actions, not by PHP.
landing:install asks where the landing lives, what it starts from, which
package manager to use, where it deploys, where visitors will reach it, and
whether the application's home page should send them there (plus, for an empty
project, which integrations to add). Then it runs create-astro, installs the
dependencies and wires the rest:
┌ What should the landing start from? ─────────────────────────┐
│ › ● An empty project: Astro's minimal template │
│ ○ An official Astro example │
│ ○ A template repository on GitHub, GitLab or Bitbucket │
│ ○ A free theme from astro.build/themes │
└──────────────────────────────────────────────────────────────┘
| What it does | Why |
|---|---|
Creates landing/ with create-astro |
Its own package.json, lockfile and build |
Publishes config/landing.php |
Where the landing is, for every other command |
Adds landing/node_modules, dist, .astro to .gitignore |
Not every template ignores them |
Adds @source not '../../landing'; to resources/css/app.css |
Tailwind v4 scans the whole project and would put the landing's classes in the application's CSS |
Writes .github/workflows/deploy-landing.yml |
If you chose a deploy target |
Adds LANDING_URL to .env and .env.example |
So the application knows where the landing is |
Points / in routes/web.php at LANDING_URL |
If you asked; see Domains and routes |
Sets base in the landing's astro.config |
If the landing is served under a path |
Nothing that exists is overwritten without --force, and .gitignore and
app.css are only ever appended to.
Starting from a template
php artisan landing:install --template=empty # Astro's minimal template php artisan landing:install --template=blog # an official example php artisan landing:install --template=onwidget/astrowind # any repository php artisan landing:install --template=https://github.com/owner/repo/tree/main/apps/site php artisan landing:install --template=gitlab:owner/repo php artisan landing:install --template=theme:landpack # a theme from astro.build/themes
theme:<slug> (or the theme's astro.build/themes/details/… URL) is looked up
in the catalog and installed from the repository the theme links to. Paid themes
have no public repository; the installer says so and links to where to buy one,
which you then install with --template=owner/repo.
Unattended
Every question has a flag. With --no-interaction the defaults are landing/,
the empty template, the application's package manager and no workflow.
php artisan landing:install --no-interaction \ --path=landing --template=basics --pm=bun \ --add=tailwind --add=sitemap \ --deploy=cloudflare-pages --branch=main \ --redirect-root
Domains and routes
The landing and the application are two sites, and the recommended setup gives each its own address:
| Production | Local | |
|---|---|---|
| Landing | https://example.com |
http://localhost:4321 |
| Application | https://app.example.com |
php artisan serve, Herd, Valet |
The application learns where the landing is from LANDING_URL
(config('landing.url')). With --redirect-root (or yes to the question),
the installer rewrites the application's home route to redirect there:
Route::get('/', function () { return config('landing.url') ? redirect()->away(config('landing.url')) : view('welcome'); })->name('home');
It is plain Laravel reading the application's own config, so it keeps working in
production, where this package is not installed, and wherever LANDING_URL is
not set it serves the page it served before.
To serve the landing under a path of the application's domain instead, such as
example.com/lp, install with --base=/lp: Astro then builds every link and
asset under /lp/, and one proxy rule on the application's domain sends /lp/
to the landing. docs/domains.md has the Nginx and Cloudflare
recipes for that, for serving the build from the application's own public/,
and for putting the application under paths of the landing's domain.
Running it
On Laravel 13.16 and later, composer run dev (which runs php artisan dev) starts the
landing as one more process, at http://localhost:4321:
landing php artisan landing:dev
server php artisan serve
queue php artisan queue:listen --tries=1 --timeout=0
vite npm run dev
On its own, or on earlier Laravel versions:
php artisan landing:dev # astro dev php artisan landing:build # astro build, into landing/dist
Both install the landing's dependencies first when node_modules is missing, so
a fresh clone needs nothing beyond the application's own setup. Set
LANDING_DEV=false to keep the landing out of composer run dev.
Deploying it
php artisan landing:workflow cloudflare-pages
Every workflow needs the repository variable LANDING_URL, the landing's
public URL. The build passes it to Astro as --site, so canonical links, Open
Graph URLs, the sitemap and robots.txt point at your domain whatever the
template's config says, and a build that still points at a placeholder such as
https://example.com is not deployed. See docs/seo.md.
| Target | Repository secrets |
|---|---|
cloudflare-pages |
CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID |
netlify |
NETLIFY_AUTH_TOKEN, NETLIFY_SITE_ID |
vercel |
VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID |
github-pages |
none |
ssh |
LANDING_SSH_HOST, LANDING_SSH_USER, LANDING_SSH_KEY, LANDING_SSH_PATH; optional LANDING_SSH_PORT, LANDING_SSH_KNOWN_HOSTS |
The workflow is filtered to landing/**, so a change to the application never
redeploys the landing. Do the reverse in your application's workflows:
on: push: branches: [main] paths-ignore: ['landing/**']
Deploying to two places is two files:
php artisan landing:workflow cloudflare-pages php artisan landing:workflow ssh --file=deploy-landing-mirror
To change every workflow this package writes, publish the stubs. They are read
from stubs/landing/workflows before the package's own:
php artisan vendor:publish --tag=landing-stubs
See docs/deployment.md for each target in detail.
Configuration
// config/landing.php 'path' => env('LANDING_PATH', 'landing'), 'url' => env('LANDING_URL'), // where visitors reach the landing 'package_manager' => env('LANDING_PACKAGE_MANAGER'), // null: the landing's lockfile decides 'dev' => [ 'enabled' => (bool) env('LANDING_DEV', true), // part of composer run dev 'name' => 'landing', ],
Commands
| Command | What it does |
|---|---|
landing:install |
Create the Astro landing and wire it into the application |
landing:dev |
Run the Astro dev server |
landing:build |
Build the landing into its dist/ |
landing:workflow |
Write a GitHub Actions workflow that deploys it |
Working with an AI agent
If the application uses Laravel Boost, this
package ships guidelines and an astro-landing-development skill. Run
php artisan boost:install and tick lauroguedes/laravel-astro-landing when it asks
which third-party guidelines and skills to install; the choice is saved to
boost.json, so later unattended runs keep it. They tell an agent that the
landing is a separate static site, which commands build and run it, how it
reaches the application, and how it deploys.
Requirements
- PHP 8.3+, Laravel 12 or 13
- Node.js 22.12+ (or Bun) for Astro
Documentation
docs/ covers templates, domains and routes, SEO, linking the landing to the application, and each deploy target.
License
MIT. See LICENSE.md.